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 v3 11/11] feat(page): implement chat bubble message rendering
Date: Sun, 24 Sep 2023 16:45:32 +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.

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

- `setupUserName()`
  This function handles for trimming user `first_name` and `last_name`.

- `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. 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 | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 255 insertions(+), 1 deletion(-)

diff --git a/index.html b/index.html
index e47a36f..64e5884 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;
@@ -351,15 +353,267 @@
 
           const usernameDivEl = document.createElement("div");
           usernameDivEl.classList.add("truncate", "text-sky-500");
-          usernameDivEl.innerText = `@${e.login}`
+          usernameDivEl.innerText = `@${e.login}`;
           userAnchorEl.appendChild(userImgEl);
           userAnchorEl.appendChild(usernameDivEl);
           memberList.appendChild(userAnchorEl);
         });
       }
 
+      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;
+        }
+        return Math.abs(hash);
+      }
+
+      function getFixedRandomColor(name) {
+        const colors = [
+          "red", "yellow", "blue", "sky", "purple", "orange",
+          "amber", "lime", "green", "emerald", "teal", "cyan",
+          "indigo", "violet", "fuchsia", "pink", "rose"
+        ];
+
+        const c = colors[hashCode(name) % colors.length];
+        return [
+          "text-" + c + "-400",
+          "border-" + c + "-400",
+        ];
+      }
+
+      function cleanMessageText(text) {
+        return text.replaceAll(/\n/g, " ");
+      }
+
+      function setupUserName(first, last) {
+        const firstName = first.trimEnd();
+        let lastName = "";
+        if(last !== null) {
+          lastName = " " + last.trimEnd();
+        }
+        return [ firstName, lastName ];
+      }
+
+      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-500", "before:duration-500", "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, lastName ] = setupUserName(
+            message.first_name,
+            message.last_name
+          );
+          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",
+                "max-w-[150px]", "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-semibold", "line-clamp-1",
+              "select-none", getColor(replyMessage)[0]
+            );
+            const [ firstName, lastName ] = setupUserName(
+              replyMessage.first_name,
+              replyMessage.last_name
+            );
+            usernameMsgReply.innerText = firstName + lastName;
+
+            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-24  9:46 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-09-24  9:45 [PATCH v3 00/11] Refactoring and Add New Features Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 01/11] style: remove extra newline at end of file Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 02/11] feat(static/img): add `profile-cover.jpg` Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 03/11] refactor(page): improve page design and add profile image cover Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 04/11] feat(page): improve UX for copying text Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 05/11] chore(page): add newlines to enhance Developer Experience Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 06/11] feat(page): add GNUWeeb member list UI Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 07/11] feat(page): add `getOrgMembers()` function Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 08/11] feat(page): add `renderMemberList()` function Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 09/11] feat(page): add initial UI for displaying recent group messages Muhammad Rizki
2023-09-24  9:45 ` [PATCH v3 10/11] feat(css): add initial CSS class styles for chat bubbles Muhammad Rizki
2023-09-24  9:45 ` Muhammad Rizki [this message]
2023-09-26  7:44 ` [PATCH v3 00/11] Refactoring and Add New Features Ammar Faizi
2023-09-26 11:06   ` 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