public inbox for [email protected]
 help / color / mirror / Atom feed
From: Muhammad Rizki <[email protected]>
To: Ammar Faizi <[email protected]>
Cc: Muhammad Rizki <[email protected]>,
	Alviro Iskandar Setiawan <[email protected]>,
	Arthur Lapz <[email protected]>, Memet Zx <[email protected]>,
	GNU/Weeb Mailing List <[email protected]>
Subject: [PATCH v1 11/11] feat(page): implement chat bubble message rendering
Date: Sat, 23 Sep 2023 03:58:17 +0700	[thread overview]
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>

This commit introduces several key functions for rendering chat
bubble messages:

- `hashCode()`:
  Code reference: https://github.com/boringdesigners/boring-avatars/blob/40a8be0d08f1a0d3aa318ef981fedbb2262d91fb/src/lib/utilities.js#L1
  Generates fixed numerical values based on alphabet characters

- `getFixedRandomColor()`
  Code reference: https://github.com/boringdesigners/boring-avatars/blob/40a8be0d08f1a0d3aa318ef981fedbb2262d91fb/src/lib/utilities.js#L35-L37
  this function is responsible for determining both the text color and
  border color. Unfortunately, Tailwind CSS requires class names to be
  static, which is why we can't use dynamic class names like
  `text-${color}-${level}`.
  Refer: https://tailwindcss.com/docs/content-configuration#dynamic-class-names

- `cleanMessageText()`
  A simple utility function that removes newline characters ('\n') from
  a given string. This function is used to prepare message text for
  rendering.

- `getRecentMessages()`
  This function handles the retrieval of recent Telegram group messages
  through an API. It also restructures the data to make it more
  accessible and readable for rendering.

- `renderRecentMessages()`
  The primary function responsible for rendering the chat bubble
  messages on the page.

Please note that the code may appear somewhat repetitive due to
constraints related to Tailwind CSS. Despite my limited experience with
vanilla JS for web development, I've made the best effort to maintain
clarity. Any suggestions or feedback for improving code organization or
readability are greatly appreciated. Your expertise in this area is
invaluable!

Signed-off-by: Muhammad Rizki <[email protected]>
---
 index.html | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 260 insertions(+)

diff --git a/index.html b/index.html
index e47a36f..81664c4 100644
--- a/index.html
+++ b/index.html
@@ -305,6 +305,8 @@
     </footer>
 
     <script lang="text/javascript">
+      const API_URL = "https://telegram-bot.teainside.org";
+
       function copyText() {
         var copyText = document.getElementById("copy-button");
         var text = document.getElementById("copy-text").innerText;
@@ -358,8 +360,266 @@
         });
       }
 
+      async function getRecentMessages() {
+        const response = await fetch(API_URL + "/zxc.php?action=get_messages&chat_id=-1001483770714&limit=50");
+        const { data } = (await response.json()).result;
+
+        const transformedData = data.map(item => {
+          return {
+            user_id: item[0],
+            username: item[1],
+            first_name: item[2],
+            last_name: item[3],
+            user_photo: item[4],
+            message_id: item[5],
+            reply_to_message_id: item[6],
+            message_type: item[7],
+            text: item[8],
+            text_entities: item[9] ? JSON.parse(item[9]) : null,
+            file: item[10],
+            date: item[11]
+          };
+        });
+
+        return transformedData.reverse();
+      }
+
+      function hashCode(name) {
+        var hash = 0;
+        for (var i = 0; i < name.length; i++) {
+            var character = name.charCodeAt(i);
+            hash = ((hash<<5)-hash)+character;
+            hash = hash & hash;
+        }
+        return Math.abs(hash);
+      }
+
+      function getFixedRandomColor(name) {
+        const textColors = [
+          "text-red-400",      "text-yellow-400",
+          "text-blue-400",     "text-sky-400",
+          "text-purple-400",   "text-orange-400",
+          "text-amber-400",    "text-lime-400",
+          "text-green-400",    "text-emerald-400",
+          "text-teal-400",     "text-cyan-400",
+          "text-indigo-400",   "text-violet-400",
+          "text-fuchsia-400",  "text-pink-400",
+          "text-rose-400"
+        ];
+        const borderColors = [
+          "border-red-400",      "border-yellow-400",
+          "border-blue-400",     "border-sky-400",
+          "border-purple-400",   "border-orange-400",
+          "border-amber-400",    "border-lime-400",
+          "border-green-400",    "border-emerald-400",
+          "border-teal-400",     "border-cyan-400",
+          "border-indigo-400",   "border-violet-400",
+          "border-fuchsia-400",  "border-pink-400",
+          "border-rose-400"
+        ];
+
+        const hash = hashCode(name);
+        const range = textColors && textColors.length;
+        const indexColor = hash % range;
+
+        return [
+          textColors[(hashCode(name)) % range],
+          borderColors[(hashCode(name)) % range]
+        ];
+      }
+
+      function cleanMessageText(text) {
+        return text.replaceAll(/\n/g, " ");
+      }
+
+      async function renderRecentMessages() {
+        const messageData = await getRecentMessages();
+        const recentMessages = document.getElementById("recent-messages");
+
+        messageData.forEach(message => {
+          const getColor = m => getFixedRandomColor(`${m.first_name}`);
+
+          const chatContainer = document.createElement("div");
+          chatContainer.id = message.message_id;
+          chatContainer.classList.add("chat", "chat-start", "max-w-[300px]");
+
+          const chatAvatar = document.createElement("div");
+          chatAvatar.classList.add("chat-image", "avatar");
+
+          const avatarRounded = document.createElement("div");
+          avatarRounded.classList.add("w-10", "rounded-full");
+
+          const avatarImg = document.createElement("img");
+          avatarImg.setAttribute("draggable", "false");
+          avatarImg.setAttribute("src", API_URL + "/storage/files/" + message.user_photo)
+
+          avatarRounded.appendChild(avatarImg);
+          chatAvatar.appendChild(avatarRounded);
+
+          const chatBubble = document.createElement("div");
+          chatBubble.id = `${message.message_id}-highlight`;
+          chatBubble.classList.add(
+            "chat-bubble", "transition", "before:transition",
+            "duration-700", "before:duration-700", "ease-in-out",
+            "before:ease-in-out", "delay-200", "before:delay-200"
+          );
+
+          const chatHeader = document.createElement("div");
+          chatHeader.classList.add(
+            "chat-header", "font-semibold", "pb-1",
+            "line-clamp-1", getColor(message)[0],
+          );
+          const firstName = message.first_name.trimEnd();
+          let lastName = "";
+          if(message.last_name !== null) {
+            lastName = " " + message.last_name.trimEnd();
+          };
+          chatHeader.innerText = `${firstName}${lastName}`;
+
+          const chatMsgContent = document.createElement("div");
+          if(message.message_type === "text") {
+            chatMsgContent.classList.add(
+              "text-neutral-300", "max-w-[150px]",
+              "max-h-min", "line-clamp-2"
+            );
+            chatMsgContent.innerText = cleanMessageText(message.text);
+          } else if(message.message_type === "photo") {
+            const imageFile = API_URL + "/storage/files/" + message.file;
+
+            const msgPhotoContainer = document.createElement("div");
+            msgPhotoContainer.classList.add("relative");
+
+            const messagePhoto = document.createElement("img");
+            messagePhoto.src = imageFile;
+            messagePhoto.classList.add("rounded-md", "mt-1", "relative");
+
+            const hoverSeeImg = document.createElement("a");
+            hoverSeeImg.href = imageFile;
+            hoverSeeImg.setAttribute("target", "_blank");
+            hoverSeeImg.setAttribute("draggable", "false");
+            hoverSeeImg.classList.add(
+              "absolute", "top-0", "w-full", "h-full", "flex", "rounded-md",
+              "items-center", "justify-center", "hover:bg-neutral-900", "uppercase",
+              "hover:bg-opacity-60", "transition", "duration-200", "ease-in-out",
+              "font-bold", "text-xl", "text-white", "opacity-0", "hover:opacity-100",
+              "select-none"
+            );
+            hoverSeeImg.innerText = "See";
+
+            msgPhotoContainer.appendChild(messagePhoto);
+            msgPhotoContainer.appendChild(hoverSeeImg);
+            chatMsgContent.appendChild(msgPhotoContainer);
+
+            if(message.text !== null) {
+              const photoCaption = document.createElement("div");
+              photoCaption.classList.add("text-neutral-300", "line-clamp-2", "mt-2");
+              photoCaption.innerText = cleanMessageText(message.text);
+              chatMsgContent.appendChild(photoCaption);
+            }
+          }
+
+          const chatFooter = document.createElement("div");
+          chatFooter.classList.add(
+            "flex", "items-center", "pt-2",
+            "justify-between", "space-x-3"
+          );
+
+          const readMore = document.createElement("a");
+          readMore.setAttribute("href", "https://t.me/gnuweeb/" + message.message_id);
+          readMore.setAttribute("draggable", "false");
+          readMore.setAttribute("target", "_blank");
+          readMore.classList.add(
+            "text-xs", "text-sky-400", "opacity-70",
+            "font-semibold", "select-none"
+          );
+          readMore.innerText = "Read more";
+          chatFooter.appendChild(readMore);
+
+          const chatDate = document.createElement("div");
+          chatDate.classList.add(
+            "flex", "justify-end", "space-x-1",
+            "text-xs", "opacity-70",
+          );
+
+          const msgTime = document.createElement("time");
+          msgTime.innerText = message.date;
+
+          chatDate.appendChild(msgTime);
+
+          chatFooter.appendChild(chatDate);
+
+          chatBubble.appendChild(chatHeader);
+
+          const replyMessages = messageData.filter(msg => {
+            return msg.message_id === message.reply_to_message_id
+          });
+          if(message.reply_to_message_id !== null && replyMessages.length > 0) {
+            const replyMessage = replyMessages[0];
+
+            const chatMsgReply = document.createElement("button");
+            chatMsgReply.onclick = () => {
+              const targetReply = document.getElementById(message.reply_to_message_id.toString());
+              const highlightReply = document.getElementById(`${message.reply_to_message_id}-highlight`)
+
+              targetReply.scrollIntoView({
+                behavior: "smooth", block: "end", inline: "nearest"
+              });
+
+              highlightReply.classList.add(
+                "!bg-neutral-700", "!scale-105"
+              );
+
+              setTimeout(() => {
+                highlightReply.classList.remove(
+                  "!bg-neutral-700", "!scale-105"
+                );
+              }, 2000);
+            }
+            chatMsgReply.classList.add(
+              "w-full", "border-l-2", "!my-2", "text-sm", "text-start",
+              "pl-2", "py-1", getColor(replyMessage)[1], "flex", "flex-col",
+              "text-neutral-300", "text-opacity-80", "hover:bg-neutral-800",
+              "rounded-r-md", "rounded-l-sm"
+            );
+
+            const usernameMsgReply = document.createElement("span");
+            usernameMsgReply.classList.add(
+              "not-italic", "font-bold",
+              "select-none", getColor(replyMessage)[0]
+            );
+            usernameMsgReply.innerText = replyMessage.first_name;
+
+            const textMsgReply = document.createElement("span");
+            textMsgReply.classList.add(
+              "line-clamp-1", "max-w-[150px]",
+              "max-h-min", "text-start"
+            );
+            textMsgReply.innerText = cleanMessageText(
+              replyMessage.text !== null ? replyMessage.text : "Photo"
+            );
+
+            chatMsgReply.appendChild(usernameMsgReply);
+            chatMsgReply.appendChild(textMsgReply);
+            chatBubble.appendChild(chatMsgReply);
+          };
+
+          chatBubble.appendChild(chatMsgContent);
+          chatBubble.appendChild(chatFooter);
+
+          chatContainer.appendChild(chatAvatar);
+          chatContainer.appendChild(chatBubble);
+
+          recentMessages.appendChild(chatContainer);
+        });
+
+        recentMessages.lastChild.scrollIntoView({
+          behavior: "instant", block: "end", inline: "nearest"
+        });
+      };
+
       document.addEventListener("DOMContentLoaded", function() {
         renderMemberList();
+        renderRecentMessages();
       });
     </script>
 
-- 
Muhammad Rizki


  parent reply	other threads:[~2023-09-22 20:59 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-09-22 20:58 [PATCH v1 00/11] Refactoring and Add New Features Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 01/11] style: remove extra newline at end of file Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 02/11] feat(static/img): add `profile-cover.jpg` Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 03/11] refactor(page): improve page design and add profile image cover Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 04/11] feat(page): improve UX for copying text Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 05/11] chore(page): add newlines to enhance Developer Experience Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 06/11] feat(page): add GNUWeeb member list UI Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 07/11] feat(page): add `getOrgMembers()` function Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 08/11] feat(page): add `renderMemberList()` function Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 09/11] feat(page): add initial UI for displaying recent group messages Muhammad Rizki
2023-09-22 20:58 ` [PATCH v1 10/11] feat(css): add initial CSS class styles for chat bubbles Muhammad Rizki
2023-09-22 20:58 ` Muhammad Rizki [this message]
2023-09-23  3:15   ` [PATCH v1 11/11] feat(page): implement chat bubble message rendering Ammar Faizi
2023-09-23  8:43     ` Muhammad Rizki

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    [email protected] \
    [email protected] \
    [email protected] \
    [email protected] \
    [email protected] \
    [email protected] \
    [email protected] \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox