From af6315b17a48ce40bfffe908aeaab74dbe77f6dd Mon Sep 17 00:00:00 2001 From: sarahelsagheir <53413980+sarahelsagheir@users.noreply.github.com> Date: Tue, 10 Sep 2024 21:39:08 +0300 Subject: [PATCH 1/5] add voice message --- src/ChatifyMessenger.php | 24 +- src/Http/Controllers/MessagesController.php | 190 +- src/assets/css/style.css | 158 ++ src/assets/js/code.js | 2811 ++++++++++--------- src/assets/js/voiceMessage.js | 364 +++ src/config/chatify.php | 1 + src/views/layouts/footerLinks.blade.php | 4 +- src/views/layouts/headLinks.blade.php | 1 + src/views/layouts/messageCard.blade.php | 24 +- src/views/layouts/sendForm.blade.php | 23 +- 10 files changed, 2149 insertions(+), 1451 deletions(-) create mode 100644 src/assets/js/voiceMessage.js diff --git a/src/ChatifyMessenger.php b/src/ChatifyMessenger.php index a89b0919..088718df 100644 --- a/src/ChatifyMessenger.php +++ b/src/ChatifyMessenger.php @@ -23,6 +23,7 @@ public function getMaxUploadSize() return config('chatify.attachments.max_upload_size') * 1048576; } + public function __construct() { $this->pusher = new Pusher( @@ -149,8 +150,15 @@ public function parseMessage($prefetchedMessage = null, $id = null) $attachmentOBJ = json_decode($msg->attachment); $attachment = $attachmentOBJ->new_name; $attachment_title = htmlentities(trim($attachmentOBJ->old_name), ENT_QUOTES, 'UTF-8'); - $ext = pathinfo($attachment, PATHINFO_EXTENSION); - $attachment_type = in_array($ext, $this->getAllowedImages()) ? 'image' : 'file'; + $ext = strtolower(pathinfo($attachment, PATHINFO_EXTENSION)); + + if (in_array($ext, $this->getAllowedImages())) { + $attachment_type = 'image'; + } else if (in_array($ext, $this->getAllowedVoiceMessages())) { + $attachment_type = 'audio'; + } else { + $attachment_type = 'file'; + } } return [ 'id' => $msg->id, @@ -442,4 +450,16 @@ public function getAttachmentUrl($attachment_name) { return self::storage()->url(config('chatify.attachments.folder') . '/' . $attachment_name); } + + + /** + * This method returns the allowed voice messages extensions + * + * @return array + */ + public function getAllowedVoiceMessages() + { + return config('chatify.attachments.allowed_voice_messages'); + } + } diff --git a/src/Http/Controllers/MessagesController.php b/src/Http/Controllers/MessagesController.php index 10964faf..d42aeb05 100644 --- a/src/Http/Controllers/MessagesController.php +++ b/src/Http/Controllers/MessagesController.php @@ -14,6 +14,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Request as FacadesRequest; use Illuminate\Support\Str; + class MessagesController extends Controller { protected $perPage = 30; @@ -89,76 +90,173 @@ public function download($fileName) } /** - * Send a message to database + * Send a message to database. * * @param Request $request * @return JsonResponse */ public function send(Request $request) { - // default variables - $error = (object)[ + $error = (object) [ 'status' => 0, - 'message' => null + 'message' => null, ]; + $attachment = null; $attachment_title = null; + $messageData = null; + $isVoiceMessage = $request->hasFile('audio'); - // if there is attachment [file] - if ($request->hasFile('file')) { - // allowed extensions - $allowed_images = Chatify::getAllowedImages(); - $allowed_files = Chatify::getAllowedFiles(); - $allowed = array_merge($allowed_images, $allowed_files); - - $file = $request->file('file'); - // check file size - if ($file->getSize() < Chatify::getMaxUploadSize()) { - if (in_array(strtolower($file->extension()), $allowed)) { - // get attachment name - $attachment_title = $file->getClientOriginalName(); - // upload attachment and store the new name - $attachment = Str::uuid() . "." . $file->extension(); - $file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name')); - } else { - $error->status = 1; - $error->message = "File extension not allowed!"; - } - } else { - $error->status = 1; - $error->message = "File size you are trying to upload is too large!"; - } + if ($isVoiceMessage) { + $this->handleVoiceMessage($request, $error, $attachment, $attachment_title); + } else { + $this->handleTextOrFileMessage($request, $error, $attachment, $attachment_title); } if (!$error->status) { - $message = Chatify::newMessage([ - 'from_id' => Auth::user()->id, - 'to_id' => $request['id'], - 'body' => htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'), - 'attachment' => ($attachment) ? json_encode((object)[ - 'new_name' => $attachment, - 'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'), - ]) : null, - ]); + $message = $this->createMessage($request, $isVoiceMessage, $attachment, $attachment_title); $messageData = Chatify::parseMessage($message); - if (Auth::user()->id != $request['id']) { - Chatify::push("private-chatify.".$request['id'], 'messaging', [ - 'from_id' => Auth::user()->id, - 'to_id' => $request['id'], - 'message' => Chatify::messageCard($messageData, true) - ]); - } + $this->pushNotification($request, $messageData); } - // send the response return Response::json([ 'status' => '200', 'error' => $error, - 'message' => Chatify::messageCard(@$messageData), + 'message' => Chatify::messageCard($messageData), 'tempID' => $request['temporaryMsgId'], ]); } + /** + * Handle a recorded voice message upload. + * + * @param Request $request + * @param object $error + * @param string|null $attachment + * @param string|null $attachment_title + * @return void + */ + private function handleVoiceMessage(Request $request, &$error, &$attachment, &$attachment_title) + { + if (!$request->hasFile('audio')) { + return; + } + + $this->storeUploadedAttachment( + $request->file('audio'), + Chatify::getAllowedVoiceMessages(), + $error, + $attachment, + $attachment_title + ); + } + + /** + * Handle a regular text message with an optional file attachment. + * + * @param Request $request + * @param object $error + * @param string|null $attachment + * @param string|null $attachment_title + * @return void + */ + private function handleTextOrFileMessage(Request $request, &$error, &$attachment, &$attachment_title) + { + if (!$request->hasFile('file')) { + return; + } + + $this->storeUploadedAttachment( + $request->file('file'), + array_merge(Chatify::getAllowedImages(), Chatify::getAllowedFiles()), + $error, + $attachment, + $attachment_title + ); + } + + /** + * Validate and store a message attachment. + * + * @param mixed $file + * @param array $allowedExtensions + * @param object $error + * @param string|null $attachment + * @param string|null $attachment_title + * @return void + */ + private function storeUploadedAttachment($file, $allowedExtensions, &$error, &$attachment, &$attachment_title) + { + if ($file->getSize() > Chatify::getMaxUploadSize()) { + $this->setError($error, "File size you are trying to upload is too large!"); + return; + } + + $extension = strtolower($file->getClientOriginalExtension()); + + if (!in_array($extension, $allowedExtensions)) { + $this->setError($error, "File extension not allowed!"); + return; + } + + $attachment = Str::uuid() . "." . $extension; + $attachment_title = $file->getClientOriginalName(); + $file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name')); + } + + /** + * Set a send-message error. + * + * @param object $error + * @param string $message + * @return void + */ + private function setError(&$error, $message) + { + $error->status = 1; + $error->message = $message; + } + + /** + * Create the chat message record. + * + * @param Request $request + * @param bool $isVoiceMessage + * @param string|null $attachment + * @param string|null $attachment_title + * @return mixed + */ + private function createMessage(Request $request, $isVoiceMessage, $attachment, $attachment_title) + { + return Chatify::newMessage([ + 'from_id' => Auth::user()->id, + 'to_id' => $request->input('id'), + 'body' => $isVoiceMessage ? '' : htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'), + 'attachment' => ($attachment) ? json_encode((object) [ + 'new_name' => $attachment, + 'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'), + ]) : null, + ]); + } + + /** + * Push a message notification to the receiver. + * + * @param Request $request + * @param array $messageData + * @return void + */ + private function pushNotification(Request $request, $messageData) + { + if (Auth::user()->id != $request->input('id')) { + Chatify::push("private-chatify." . $request->input('id'), 'messaging', [ + 'from_id' => Auth::user()->id, + 'to_id' => $request->input('id'), + 'message' => Chatify::messageCard($messageData, true), + ]); + } + } + /** * fetch [user/group] messages from database * diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 4565b6b8..aab72f33 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -129,6 +129,8 @@ body { display: flex; flex-direction: column; gap: 5px; + position: relative; + z-index: 0; overflow: hidden; width: 100%; } @@ -373,6 +375,8 @@ body { .messenger-infoView { display: block; overflow: auto; + position: relative; + z-index: 3; width: 40%; min-width: 200px; } @@ -1029,6 +1033,7 @@ div.loadingPlaceholder-date { right: 0; top: 0; bottom: 0; + z-index: 10; max-width: 334px; } } @@ -1184,3 +1189,156 @@ div.loadingPlaceholder-date { border-radius: 4px; background: transparent; } + + +/* +***************************************** +* microphone +***************************************** +*/ +.microphone-button { + position: relative; + border: 2px solid var(--primary-color); + background-color: var(--primary-color); + border-radius: 50%; + padding: 15px; + cursor: pointer; + outline: none; + transition: transform 0.2s ease; +} +.microphone-icon { + position: relative; + font-size: 24px; + color: white; +} + +/* +***************************************** +* waveSurfer +***************************************** +*/ +.audio-wrap { + display: flex; + align-items: center; + border-radius: 20px; + justify-content: space-between; + position: relative; + z-index: 0; + cursor: pointer; +} + +.controls { + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 25%; + margin-bottom: 15px; + padding: 15px; + width: 13em; + height: 3em; + border-radius: 25px; + box-shadow: 0px 6px 11px rgba(18, 67, 105, 0.03); +} + +.controls .btn-toggle-play { + background-color: transparent; + border: none; + cursor: pointer; + width: 10%; + height: 50px; + display: flex; + justify-content: center; + align-items: center; +} + +.controls .btn-toggle-play i { + font-size: 20px; +} + +.waveform { + width: 80%; + overflow: hidden; + position: relative; + z-index: 0; + cursor: pointer; +} + +.waveform canvas, +.waveform wave { + position: relative; + z-index: 0; + cursor: pointer; +} + +.controls .duration { + font-size: 15px; + font-weight: 500; + margin-left: 15px; + width: 10%; +} + +/* + * Recording Slide Progress +*/ +.recording-ui { + width: 100%; + padding: 10px 0; +} + +.recording-slider-wrapper { + background-color: var(--primary-color); + border-radius: 18px; + display: flex; + align-items: center; + width: 100%; + position: relative; + overflow: hidden; +} + +.recording-slider-content { + display: flex; + align-items: center; + width: 100%; + position: relative; + z-index: 2; +} + +.recording-progress-bar { + position: absolute; + left: 0; + top: 0; + height: 100%; + background-color: rgba(241, 243, 245, 0.2); + transition: width 0.1s linear; + z-index: 1; +} + +.stop-button, +.trash-button, +.send-record-button { + background: none; + border: none; + cursor: pointer; + padding: 0 10px; + z-index: 2; +} + +.fa-trash, +.fa-paper-plane { + color: var(--primary-color) !important; +} + +.fa-stop, +.fa-play, +.fa-pause { + color: white !important; + font-size: 15px !important; +} + +.recording-time { + font-size: 14px; + color: #f4f6f8 !important; + margin: 0 10px; + min-width: 40px; + z-index: 2; +} diff --git a/src/assets/js/code.js b/src/assets/js/code.js index 6501b5a0..f97206a7 100644 --- a/src/assets/js/code.js +++ b/src/assets/js/code.js @@ -3,95 +3,95 @@ * Global variables *------------------------------------------------------------- */ -var messenger, - typingTimeout, - typingNow = 0, - temporaryMsgId = 0, - defaultAvatarInSettings = null, - messengerColor, - dark_mode, - messages_page = 1; + var messenger, + typingTimeout, + typingNow = 0, + temporaryMsgId = 0, + defaultAvatarInSettings = null, + messengerColor, + dark_mode, + messages_page = 1; const messagesContainer = $(".messenger-messagingView .m-body"), - messengerTitleDefault = $(".messenger-headTitle").text(), - messageInputContainer = $(".messenger-sendCard"), - messageInput = $("#message-form .m-send"), - auth_id = $("meta[name=url]").attr("data-user"), - url = $("meta[name=url]").attr("content"), - messengerTheme = $("meta[name=messenger-theme]").attr("content"), - defaultMessengerColor = $("meta[name=messenger-color]").attr("content"), - csrfToken = $('meta[name="csrf-token"]').attr("content"); + messengerTitleDefault = $(".messenger-headTitle").text(), + messageInputContainer = $(".messenger-sendCard"), + messageInput = $("#message-form .m-send"), + auth_id = $("meta[name=url]").attr("data-user"), + url = $("meta[name=url]").attr("content"), + messengerTheme = $("meta[name=messenger-theme]").attr("content"), + defaultMessengerColor = $("meta[name=messenger-color]").attr("content"), + csrfToken = $('meta[name="csrf-token"]').attr("content"); const getMessengerId = () => $("meta[name=id]").attr("content"); const setMessengerId = (id) => $("meta[name=id]").attr("content", id); /** - *------------------------------------------------------------- - * Pusher initialization - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Pusher initialization +*------------------------------------------------------------- +*/ Pusher.logToConsole = chatify.pusher.debug; const pusher = new Pusher(chatify.pusher.key, { - encrypted: chatify.pusher.options.encrypted, - cluster: chatify.pusher.options.cluster, - wsHost: chatify.pusher.options.host, - wsPort: chatify.pusher.options.port, - wssPort: chatify.pusher.options.port, - forceTLS: chatify.pusher.options.useTLS, - authEndpoint: chatify.pusherAuthEndpoint, - auth: { - headers: { - "X-CSRF-TOKEN": csrfToken, - }, - }, + encrypted: chatify.pusher.options.encrypted, + cluster: chatify.pusher.options.cluster, + wsHost: chatify.pusher.options.host, + wsPort: chatify.pusher.options.port, + wssPort: chatify.pusher.options.port, + forceTLS: chatify.pusher.options.useTLS, + authEndpoint: chatify.pusherAuthEndpoint, + auth: { + headers: { + "X-CSRF-TOKEN": csrfToken, + }, + }, }); /** - *------------------------------------------------------------- - * Re-usable methods - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Re-usable methods +*------------------------------------------------------------- +*/ const escapeHtml = (unsafe) => { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">"); + return unsafe + .replace(/&/g, "&") + .replace(//g, ">"); }; function actionOnScroll(selector, callback, topScroll = false) { - $(selector).on("scroll", function () { - let element = $(this).get(0); - const condition = topScroll - ? element.scrollTop == 0 - : element.scrollTop + element.clientHeight >= element.scrollHeight; - if (condition) { - callback(); - } - }); + $(selector).on("scroll", function () { + let element = $(this).get(0); + const condition = topScroll + ? element.scrollTop == 0 + : element.scrollTop + element.clientHeight >= element.scrollHeight; + if (condition) { + callback(); + } + }); } function routerPush(title, url) { - $("meta[name=url]").attr("content", url); - return window.history.pushState({}, title || document.title, url); + $("meta[name=url]").attr("content", url); + return window.history.pushState({}, title || document.title, url); } function updateSelectedContact(user_id) { user_id = user_id || getMessengerId(); -$(document).find(".messenger-list-item").removeClass("m-list-active"); -$(document) - .find( + $(document).find(".messenger-list-item").removeClass("m-list-active"); + $(document) + .find( ".messenger-list-item[data-contact=" + (user_id) + "]" - ) - .addClass("m-list-active"); + ) + .addClass("m-list-active"); - if (user_id != 0) { - IDinfo(user_id); - } + if (user_id != 0) { + IDinfo(user_id); } + } /** - *------------------------------------------------------------- - * Global Templates - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Global Templates +*------------------------------------------------------------- +*/ // Loading svg function loadingSVG(size = "25px", className = "", style = "") { - return ` + return ` @@ -105,18 +105,18 @@ function loadingSVG(size = "25px", className = "", style = "") { `; } function loadingWithContainer(className) { - return `
${loadingSVG( - "25px", - "", - "margin:auto" - )}
`; + return `
${loadingSVG( + "25px", + "", + "margin:auto" + )}
`; } // loading placeholder for users list item function listItemLoading(items) { - let template = ""; - for (let i = 0; i < items; i++) { - template += ` + let template = ""; + for (let i = 0; i < items; i++) { + template += `
@@ -133,15 +133,15 @@ function listItemLoading(items) {
`; - } - return template; + } + return template; } // loading placeholder for avatars function avatarLoading(items) { - let template = ""; - for (let i = 0; i < items; i++) { - template += ` + let template = ""; + for (let i = 0; i < items; i++) { + template += `
@@ -156,290 +156,290 @@ function avatarLoading(items) {
`; - } - return template; + } + return template; } // While sending a message, show this temporary message card. function sendTempMessageCard(message, id) { - return ` -
-
-
- ${message} - - - -
-
-
+ return ` +
+
+
+ ${message} + + + +
+
+
`; } // upload image preview card. function attachmentTemplate(fileType, fileName, imgURL = null) { - if (fileType != "image") { - return ( - ` -
- -

` + - escapeHtml(fileName) + - `

-
+ if (fileType != "image") { + return ( + ` +
+ +

` + + escapeHtml(fileName) + + `

+
` - ); - } else { - return ( - ` + ); + } else { + return ( + `
- -
-

` + - escapeHtml(fileName) + - `

+ +
+

` + + escapeHtml(fileName) + + `

` - ); - } + ); + } } // Active Status Circle function activeStatusCircle() { - return ``; + return ``; } /** - *------------------------------------------------------------- - * Css Media Queries [For responsive design] - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Css Media Queries [For responsive design] +*------------------------------------------------------------- +*/ $(window).resize(function () { - cssMediaQueries(); + cssMediaQueries(); }); function cssMediaQueries() { - if (window.matchMedia("(min-width: 980px)").matches) { - $(".messenger-listView").removeAttr("style"); - } - if (window.matchMedia("(max-width: 980px)").matches) { - $("body") - .find(".messenger-list-item") - .find("tr[data-action]") - .attr("data-action", "1"); - $("body").find(".favorite-list-item").find("div").attr("data-action", "1"); - } else { - $("body") - .find(".messenger-list-item") - .find("tr[data-action]") - .attr("data-action", "0"); - $("body").find(".favorite-list-item").find("div").attr("data-action", "0"); - } + if (window.matchMedia("(min-width: 980px)").matches) { + $(".messenger-listView").removeAttr("style"); + } + if (window.matchMedia("(max-width: 980px)").matches) { + $("body") + .find(".messenger-list-item") + .find("tr[data-action]") + .attr("data-action", "1"); + $("body").find(".favorite-list-item").find("div").attr("data-action", "1"); + } else { + $("body") + .find(".messenger-list-item") + .find("tr[data-action]") + .attr("data-action", "0"); + $("body").find(".favorite-list-item").find("div").attr("data-action", "0"); + } } /** - *------------------------------------------------------------- - * App Modal - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* App Modal +*------------------------------------------------------------- +*/ let app_modal = function ({ - show = true, - name, - data = 0, - buttons = true, - header = null, - body = null, + show = true, + name, + data = 0, + buttons = true, + header = null, + body = null, }) { - const modal = $(".app-modal[data-name=" + name + "]"); - // header - header ? modal.find(".app-modal-header").html(header) : ""; - - // body - body ? modal.find(".app-modal-body").html(body) : ""; - - // buttons - buttons == true - ? modal.find(".app-modal-footer").show() - : modal.find(".app-modal-footer").hide(); - - // show / hide - if (show == true) { - modal.show(); - $(".app-modal-card[data-name=" + name + "]").addClass("app-show-modal"); - $(".app-modal-card[data-name=" + name + "]").attr("data-modal", data); - } else { - modal.hide(); - $(".app-modal-card[data-name=" + name + "]").removeClass("app-show-modal"); - $(".app-modal-card[data-name=" + name + "]").attr("data-modal", data); - } + const modal = $(".app-modal[data-name=" + name + "]"); + // header + header ? modal.find(".app-modal-header").html(header) : ""; + + // body + body ? modal.find(".app-modal-body").html(body) : ""; + + // buttons + buttons == true + ? modal.find(".app-modal-footer").show() + : modal.find(".app-modal-footer").hide(); + + // show / hide + if (show == true) { + modal.show(); + $(".app-modal-card[data-name=" + name + "]").addClass("app-show-modal"); + $(".app-modal-card[data-name=" + name + "]").attr("data-modal", data); + } else { + modal.hide(); + $(".app-modal-card[data-name=" + name + "]").removeClass("app-show-modal"); + $(".app-modal-card[data-name=" + name + "]").attr("data-modal", data); + } }; /** - *------------------------------------------------------------- - * Slide to bottom on [action] - e.g. [message received, sent, loaded] - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Slide to bottom on [action] - e.g. [message received, sent, loaded] +*------------------------------------------------------------- +*/ function scrollToBottom(container) { - $(container) - .stop() - .animate({ - scrollTop: $(container)[0].scrollHeight, - }); + $(container) + .stop() + .animate({ + scrollTop: $(container)[0].scrollHeight, + }); } /** - *------------------------------------------------------------- - * click and drag to scroll - function - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* click and drag to scroll - function +*------------------------------------------------------------- +*/ function hScroller(scroller) { - const slider = document.querySelector(scroller); - let isDown = false; - let startX; - let scrollLeft; - - slider.addEventListener("mousedown", (e) => { - isDown = true; - startX = e.pageX - slider.offsetLeft; - scrollLeft = slider.scrollLeft; - }); - slider.addEventListener("mouseleave", () => { - isDown = false; - }); - slider.addEventListener("mouseup", () => { - isDown = false; - }); - slider.addEventListener("mousemove", (e) => { - if (!isDown) return; - e.preventDefault(); - const x = e.pageX - slider.offsetLeft; - const walk = (x - startX) * 1; - slider.scrollLeft = scrollLeft - walk; - }); + const slider = document.querySelector(scroller); + let isDown = false; + let startX; + let scrollLeft; + + slider.addEventListener("mousedown", (e) => { + isDown = true; + startX = e.pageX - slider.offsetLeft; + scrollLeft = slider.scrollLeft; + }); + slider.addEventListener("mouseleave", () => { + isDown = false; + }); + slider.addEventListener("mouseup", () => { + isDown = false; + }); + slider.addEventListener("mousemove", (e) => { + if (!isDown) return; + e.preventDefault(); + const x = e.pageX - slider.offsetLeft; + const walk = (x - startX) * 1; + slider.scrollLeft = scrollLeft - walk; + }); } /** - *------------------------------------------------------------- - * Disable/enable message form fields, messaging container... - * on load info or if needed elsewhere. - * - * Default : true - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Disable/enable message form fields, messaging container... +* on load info or if needed elsewhere. +* +* Default : true +*------------------------------------------------------------- +*/ function disableOnLoad(disable = true) { - if (disable) { - // hide star button - $(".add-to-favorite").hide(); - // hide send card - $(".messenger-sendCard").hide(); - // add loading opacity to messages container - messagesContainer.css("opacity", ".5"); - // disable message form fields - messageInput.attr("readonly", "readonly"); - $("#message-form button").attr("disabled", "disabled"); - $(".upload-attachment").attr("disabled", "disabled"); - } else { - // show star button - if (getMessengerId() != auth_id) { - $(".add-to-favorite").show(); - } - // show send card - $(".messenger-sendCard").show(); - // remove loading opacity to messages container - messagesContainer.css("opacity", "1"); - // enable message form fields - messageInput.removeAttr("readonly"); - $("#message-form button").removeAttr("disabled"); - $(".upload-attachment").removeAttr("disabled"); - } + if (disable) { + // hide star button + $(".add-to-favorite").hide(); + // hide send card + $(".messenger-sendCard").hide(); + // add loading opacity to messages container + messagesContainer.css("opacity", ".5"); + // disable message form fields + messageInput.attr("readonly", "readonly"); + $("#message-form button").attr("disabled", "disabled"); + $(".upload-attachment").attr("disabled", "disabled"); + } else { + // show star button + if (getMessengerId() != auth_id) { + $(".add-to-favorite").show(); + } + // show send card + $(".messenger-sendCard").show(); + // remove loading opacity to messages container + messagesContainer.css("opacity", "1"); + // enable message form fields + messageInput.removeAttr("readonly"); + $("#message-form button").removeAttr("disabled"); + $(".upload-attachment").removeAttr("disabled"); + } } /** - *------------------------------------------------------------- - * Error message card - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Error message card +*------------------------------------------------------------- +*/ function errorMessageCard(id) { - messagesContainer - .find(".message-card[data-id=" + id + "]") - .addClass("mc-error"); - messagesContainer - .find(".message-card[data-id=" + id + "]") - .find("svg.loadingSVG") - .remove(); - messagesContainer - .find(".message-card[data-id=" + id + "] p") - .prepend(''); + messagesContainer + .find(".message-card[data-id=" + id + "]") + .addClass("mc-error"); + messagesContainer + .find(".message-card[data-id=" + id + "]") + .find("svg.loadingSVG") + .remove(); + messagesContainer + .find(".message-card[data-id=" + id + "] p") + .prepend(''); } /** - *------------------------------------------------------------- - * Fetch id data (user/group) and update the view - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Fetch id data (user/group) and update the view +*------------------------------------------------------------- +*/ function IDinfo(id) { - // clear temporary message id - temporaryMsgId = 0; - // clear typing now - typingNow = 0; - // show loading bar - NProgress.start(); - // disable message form - disableOnLoad(); - if (messenger != 0) { - // get shared photos - getSharedPhotos(id); - // Get info - $.ajax({ - url: url + "/idInfo", - method: "POST", - data: { _token: csrfToken, id }, - dataType: "JSON", - success: (data) => { - if (!data?.fetch) { - NProgress.done(); - NProgress.remove(); - return; - } - // avatar photo - $(".messenger-infoView") - .find(".avatar") - .css("background-image", 'url("' + data.user_avatar + '")'); - $(".header-avatar").css( - "background-image", - 'url("' + data.user_avatar + '")' - ); - // Show shared and actions - $(".messenger-infoView-btns .delete-conversation").show(); - $(".messenger-infoView-shared").show(); - // fetch messages - fetchMessages(id, true); - // focus on messaging input - messageInput.focus(); - // update info in view - $(".messenger-infoView .info-name").text(data.fetch.name); - $(".m-header-messaging .user-name").text(data.fetch.name); - // Star status - data.favorite > 0 - ? $(".add-to-favorite").addClass("favorite") - : $(".add-to-favorite").removeClass("favorite"); - // form reset and focus - $("#message-form").trigger("reset"); - cancelAttachment(); - messageInput.focus(); - }, - error: () => { - console.error("Couldn't fetch user data!"); - // remove loading bar - NProgress.done(); - NProgress.remove(); - }, - }); - } else { - // remove loading bar - NProgress.done(); - NProgress.remove(); - } + // clear temporary message id + temporaryMsgId = 0; + // clear typing now + typingNow = 0; + // show loading bar + NProgress.start(); + // disable message form + disableOnLoad(); + if (messenger != 0) { + // get shared photos + getSharedPhotos(id); + // Get info + $.ajax({ + url: url + "/idInfo", + method: "POST", + data: { _token: csrfToken, id }, + dataType: "JSON", + success: (data) => { + if (!data?.fetch) { + NProgress.done(); + NProgress.remove(); + return; + } + // avatar photo + $(".messenger-infoView") + .find(".avatar") + .css("background-image", 'url("' + data.user_avatar + '")'); + $(".header-avatar").css( + "background-image", + 'url("' + data.user_avatar + '")' + ); + // Show shared and actions + $(".messenger-infoView-btns .delete-conversation").show(); + $(".messenger-infoView-shared").show(); + // fetch messages + fetchMessages(id, true); + // focus on messaging input + messageInput.focus(); + // update info in view + $(".messenger-infoView .info-name").text(data.fetch.name); + $(".m-header-messaging .user-name").text(data.fetch.name); + // Star status + data.favorite > 0 + ? $(".add-to-favorite").addClass("favorite") + : $(".add-to-favorite").removeClass("favorite"); + // form reset and focus + $("#message-form").trigger("reset"); + cancelAttachment(); + messageInput.focus(); + }, + error: () => { + console.error("Couldn't fetch user data!"); + // remove loading bar + NProgress.done(); + NProgress.remove(); + }, + }); + } else { + // remove loading bar + NProgress.done(); + NProgress.remove(); + } } /** @@ -447,180 +447,199 @@ function IDinfo(id) { * Send message function *------------------------------------------------------------- */ -function sendMessage() { - temporaryMsgId += 1; - let tempID = `temp_${temporaryMsgId}`; - let hasFile = !!$(".upload-attachment").val(); - const inputValue = $.trim(messageInput.val()); - if (inputValue.length > 0 || hasFile) { - const formData = new FormData($("#message-form")[0]); - formData.append("id", getMessengerId()); - formData.append("temporaryMsgId", tempID); - formData.append("_token", csrfToken); - $.ajax({ - url: $("#message-form").attr("action"), - method: "POST", - data: formData, - dataType: "JSON", - processData: false, - contentType: false, - beforeSend: () => { - // remove message hint - $(".messages").find(".message-hint").hide(); - // append a temporary message card - if (hasFile) { - messagesContainer - .find(".messages") - .append( - sendTempMessageCard( - sanitizeInput(inputValue) + "\n" + loadingSVG("28px"), - tempID - ) - ); - } else { - messagesContainer - .find(".messages") - .append(sendTempMessageCard(sanitizeInput(inputValue), tempID)); - } - // scroll to bottom - scrollToBottom(messagesContainer); - messageInput.css({ height: "42px" }); - // form reset and focus - $("#message-form").trigger("reset"); - cancelAttachment(); - messageInput.focus(); - }, - success: (data) => { - if (data.error > 0) { - // message card error status - errorMessageCard(tempID); - console.error(data.error_msg); - } else { - // update contact item - updateContactItem(getMessengerId()); - // temporary message card - const tempMsgCardElement = messagesContainer.find( - `.message-card[data-id=${data.tempID}]` - ); - // add the message card coming from the server before the temp-card - tempMsgCardElement.before(data.message); - // then, remove the temporary message card - tempMsgCardElement.remove(); - // scroll to bottom - scrollToBottom(messagesContainer); - // send contact item updates - sendContactItemUpdates(true); - } - }, - error: () => { - // message card error status - errorMessageCard(tempID); - // error log - console.error( - "Failed sending the message! Please, check your server response." - ); - }, - }); - } - return false; +function sendMessage(isVoiceMessage = false, audioBlob = null, duration = null, fileName = "voice-message.webm") { + temporaryMsgId += 1; + const tempID = `temp_${temporaryMsgId}`; + const receiverId = getMessengerId(); + const hasFile = !!$(".upload-attachment").val(); + const inputValue = $.trim(messageInput.val()); + let formData; + + if (isVoiceMessage && audioBlob) { + formData = new FormData(); + formData.append("audio", audioBlob, fileName); + formData.append("id", receiverId); + formData.append("temporaryMsgId", tempID); + formData.append("_token", csrfToken); + } else { + if (inputValue.length === 0 && !hasFile) { + return false; + } + + formData = new FormData($("#message-form")[0]); + formData.append("id", receiverId); + formData.append("temporaryMsgId", tempID); + formData.append("_token", csrfToken); + + } + + $.ajax({ + url: $("#message-form").attr("action"), + method: "POST", + data: formData, + dataType: "JSON", + processData: false, + contentType: false, + beforeSend: () => { + $(".messages").find(".message-hint").hide(); + + const tempMessage = isVoiceMessage + ? `${loadingSVG("18px")} Sending voice message` + : `${sanitizeInput(inputValue)}${hasFile ? "\n" + loadingSVG("28px") : ""}`; + + messagesContainer + .find(".messages") + .append(sendTempMessageCard(tempMessage, tempID)); + + scrollToBottom(messagesContainer); + messageInput.css({ height: "42px" }); + + if (!isVoiceMessage) { + $("#message-form").trigger("reset"); + cancelAttachment(); + messageInput.focus(); + } + }, + success: (data) => { + if (data.error?.status || data.error > 0) { + errorMessageCard(tempID); + console.error(data.error?.message || data.error_msg); + } else { + updateContactItem(receiverId); + + const tempMsgCardElement = messagesContainer.find( + `.message-card[data-id=${data.tempID}]` + ); + + const message = duration + ? data.message.replace(/()\d{1,2}:\d{2}(<\/span>)/, `$1${duration}$2`) + : data.message; + + tempMsgCardElement.before(message); + tempMsgCardElement.remove(); + + const actualMessageIdMatch = message.match(/data-id="([a-z0-9\-]+)"/); + const actualMessageId = actualMessageIdMatch ? actualMessageIdMatch[1] : null; + + if (actualMessageId) { + setTimeout(() => { + initializeAudioPlayer(actualMessageId); + }, 100); + } + + scrollToBottom(messagesContainer); + sendContactItemUpdates(true); + } + }, + error: () => { + errorMessageCard(tempID); + console.error("Failed sending the message! Please, check your server response."); + }, + }); + + return false; } - /** - *------------------------------------------------------------- - * Fetch messages from database - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Fetch messages from database +*------------------------------------------------------------- +*/ let messagesPage = 1; let noMoreMessages = false; let messagesLoading = false; function setMessagesLoading(loading = false) { - if (!loading) { - messagesContainer.find(".messages").find(".loading-messages").remove(); - NProgress.done(); - NProgress.remove(); - } else { - messagesContainer - .find(".messages") - .prepend(loadingWithContainer("loading-messages")); - } - messagesLoading = loading; + if (!loading) { + messagesContainer.find(".messages").find(".loading-messages").remove(); + NProgress.done(); + NProgress.remove(); + } else { + messagesContainer + .find(".messages") + .prepend(loadingWithContainer("loading-messages")); + } + messagesLoading = loading; } function fetchMessages(id, newFetch = false) { - if (newFetch) { - messagesPage = 1; - noMoreMessages = false; - } - if (messenger != 0 && !noMoreMessages && !messagesLoading) { - const messagesElement = messagesContainer.find(".messages"); - setMessagesLoading(true); - $.ajax({ - url: url + "/fetchMessages", - method: "POST", - data: { - _token: csrfToken, - id: id, - page: messagesPage, - }, - dataType: "JSON", - success: (data) => { - setMessagesLoading(false); - if (messagesPage == 1) { - messagesElement.html(data.messages); - scrollToBottom(messagesContainer); - } else { - const lastMsg = messagesElement.find( - messagesElement.find(".message-card")[0] - ); - const curOffset = - lastMsg.offset().top - messagesContainer.scrollTop(); - messagesElement.prepend(data.messages); - messagesContainer.scrollTop(lastMsg.offset().top - curOffset); - } - // trigger seen event - makeSeen(true); - // Pagination lock & messages page - noMoreMessages = messagesPage >= data?.last_page; - if (!noMoreMessages) messagesPage += 1; - // Enable message form if messenger not = 0; means if data is valid - if (messenger != 0) { - disableOnLoad(false); - } - }, - error: (error) => { - setMessagesLoading(false); - console.error(error); - }, - }); - } + if (newFetch) { + messagesPage = 1; + noMoreMessages = false; + } + if (messenger != 0 && !noMoreMessages && !messagesLoading) { + const messagesElement = messagesContainer.find(".messages"); + setMessagesLoading(true); + $.ajax({ + url: url + "/fetchMessages", + method: "POST", + data: { + _token: csrfToken, + id: id, + page: messagesPage, + }, + dataType: "JSON", + success: (data) => { + setMessagesLoading(false); + if (messagesPage == 1) { + messagesElement.html(data.messages); + // Update voice messages and initialize WaveSurfer players. + if (typeof renderWavesurfers === "function") { + renderWavesurfers(); + } + + scrollToBottom(messagesContainer); + } else { + const lastMsg = messagesElement.find( + messagesElement.find(".message-card")[0] + ); + const curOffset = + lastMsg.offset().top - messagesContainer.scrollTop(); + messagesElement.prepend(data.messages); + messagesContainer.scrollTop(lastMsg.offset().top - curOffset); + } + // trigger seen event + makeSeen(true); + // Pagination lock & messages page + noMoreMessages = messagesPage >= data?.last_page; + if (!noMoreMessages) messagesPage += 1; + // Enable message form if messenger not = 0; means if data is valid + if (messenger != 0) { + disableOnLoad(false); + } + }, + error: (error) => { + setMessagesLoading(false); + console.error(error); + }, + }); + } } /** - *------------------------------------------------------------- - * Cancel file attached in the message. - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Cancel file attached in the message. +*------------------------------------------------------------- +*/ function cancelAttachment() { - $(".messenger-sendCard").find(".attachment-preview").remove(); - $(".upload-attachment").replaceWith( - $(".upload-attachment").val("").clone(true) - ); + $(".messenger-sendCard").find(".attachment-preview").remove(); + $(".upload-attachment").replaceWith( + $(".upload-attachment").val("").clone(true) + ); } /** - *------------------------------------------------------------- - * Cancel updating avatar in settings - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Cancel updating avatar in settings +*------------------------------------------------------------- +*/ function cancelUpdatingAvatar() { - $(".upload-avatar-preview").css("background-image", defaultAvatarInSettings); - $(".upload-avatar").replaceWith($(".upload-avatar").val("").clone(true)); + $(".upload-avatar-preview").css("background-image", defaultAvatarInSettings); + $(".upload-avatar").replaceWith($(".upload-avatar").val("").clone(true)); } /** - *------------------------------------------------------------- - * Pusher channels and event listening.. - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Pusher channels and event listening.. +*------------------------------------------------------------- +*/ // subscribe to the channel const channelName = "private-chatify"; @@ -629,76 +648,76 @@ var clientSendChannel; var clientListenChannel; function initClientChannel() { - if (getMessengerId()) { - clientSendChannel = pusher.subscribe(`${channelName}.${getMessengerId()}`); - clientListenChannel = pusher.subscribe(`${channelName}.${auth_id}`); - } + if (getMessengerId()) { + clientSendChannel = pusher.subscribe(`${channelName}.${getMessengerId()}`); + clientListenChannel = pusher.subscribe(`${channelName}.${auth_id}`); + } } initClientChannel(); // Listen to messages, and append if data received channel.bind("messaging", function (data) { - if (data.from_id == getMessengerId() && data.to_id == auth_id) { - $(".messages").find(".message-hint").remove(); - messagesContainer.find(".messages").append(data.message); - scrollToBottom(messagesContainer); - makeSeen(true); - // remove unseen counter for the user from the contacts list - $(".messenger-list-item[data-contact=" + getMessengerId() + "]") - .find("tr>td>b") - .remove(); - } - - playNotificationSound( - "new_message", - !(data.from_id == getMessengerId() && data.to_id == auth_id) - ); + if (data.from_id == getMessengerId() && data.to_id == auth_id) { + $(".messages").find(".message-hint").remove(); + messagesContainer.find(".messages").append(data.message); + scrollToBottom(messagesContainer); + makeSeen(true); + // remove unseen counter for the user from the contacts list + $(".messenger-list-item[data-contact=" + getMessengerId() + "]") + .find("tr>td>b") + .remove(); + } + + playNotificationSound( + "new_message", + !(data.from_id == getMessengerId() && data.to_id == auth_id) + ); }); // listen to typing indicator clientListenChannel.bind("client-typing", function (data) { - if (data.from_id == getMessengerId() && data.to_id == auth_id) { - data.typing == true - ? messagesContainer.find(".typing-indicator").show() - : messagesContainer.find(".typing-indicator").hide(); - } - // scroll to bottom - scrollToBottom(messagesContainer); + if (data.from_id == getMessengerId() && data.to_id == auth_id) { + data.typing == true + ? messagesContainer.find(".typing-indicator").show() + : messagesContainer.find(".typing-indicator").hide(); + } + // scroll to bottom + scrollToBottom(messagesContainer); }); // listen to seen event clientListenChannel.bind("client-seen", function (data) { - if (data.from_id == getMessengerId() && data.to_id == auth_id) { - if (data.seen == true) { - $(".message-time") - .find(".fa-check") - .before(' '); - $(".message-time").find(".fa-check").remove(); - } - } + if (data.from_id == getMessengerId() && data.to_id == auth_id) { + if (data.seen == true) { + $(".message-time") + .find(".fa-check") + .before(' '); + $(".message-time").find(".fa-check").remove(); + } + } }); // listen to contact item updates event clientListenChannel.bind("client-contactItem", function (data) { - if (data.to == auth_id) { - if (data.update) { - updateContactItem(data.from); - } else { - console.error("Can not update contact item!"); - } - } + if (data.to == auth_id) { + if (data.update) { + updateContactItem(data.from); + } else { + console.error("Can not update contact item!"); + } + } }); // listen on message delete event clientListenChannel.bind("client-messageDelete", function (data) { - $("body").find(`.message-card[data-id=${data.id}]`).remove(); + $("body").find(`.message-card[data-id=${data.id}]`).remove(); }); // listen on delete conversation event clientListenChannel.bind("client-deleteConversation", function (data) { - if (data.from == getMessengerId() && data.to == auth_id) { - $("body").find(`.messages`).html(""); - $(".messages").find(".message-hint").show(); - } + if (data.from == getMessengerId() && data.to == auth_id) { + $("body").find(`.messages`).html(""); + $(".messages").find(".message-hint").show(); + } }); // ------------------------------------- // presence channel [User Active Status] @@ -706,548 +725,548 @@ var activeStatusChannel = pusher.subscribe("presence-activeStatus"); // Joined activeStatusChannel.bind("pusher:member_added", function (member) { - setActiveStatus(1); - $(".messenger-list-item[data-contact=" + member.id + "]") - .find(".activeStatus") - .remove(); - $(".messenger-list-item[data-contact=" + member.id + "]") - .find(".avatar") - .before(activeStatusCircle()); + setActiveStatus(1); + $(".messenger-list-item[data-contact=" + member.id + "]") + .find(".activeStatus") + .remove(); + $(".messenger-list-item[data-contact=" + member.id + "]") + .find(".avatar") + .before(activeStatusCircle()); }); // Leaved activeStatusChannel.bind("pusher:member_removed", function (member) { - setActiveStatus(0); - $(".messenger-list-item[data-contact=" + member.id + "]") - .find(".activeStatus") - .remove(); + setActiveStatus(0); + $(".messenger-list-item[data-contact=" + member.id + "]") + .find(".activeStatus") + .remove(); }); function handleVisibilityChange() { - if (!document.hidden) { - makeSeen(true); - } + if (!document.hidden) { + makeSeen(true); + } } document.addEventListener("visibilitychange", handleVisibilityChange, false); /** - *------------------------------------------------------------- - * Trigger typing event - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Trigger typing event +*------------------------------------------------------------- +*/ function isTyping(status) { - return clientSendChannel.trigger("client-typing", { - from_id: auth_id, // Me - to_id: getMessengerId(), // Messenger - typing: status, - }); + return clientSendChannel.trigger("client-typing", { + from_id: auth_id, // Me + to_id: getMessengerId(), // Messenger + typing: status, + }); } /** - *------------------------------------------------------------- - * Trigger seen event - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Trigger seen event +*------------------------------------------------------------- +*/ function makeSeen(status) { - if (document?.hidden) { - return; - } - // remove unseen counter for the user from the contacts list - $(".messenger-list-item[data-contact=" + getMessengerId() + "]") - .find("tr>td>b") - .remove(); - // seen - $.ajax({ - url: url + "/makeSeen", - method: "POST", - data: { _token: csrfToken, id: getMessengerId() }, - dataType: "JSON", - }); - return clientSendChannel.trigger("client-seen", { - from_id: auth_id, // Me - to_id: getMessengerId(), // Messenger - seen: status, - }); + if (document?.hidden) { + return; + } + // remove unseen counter for the user from the contacts list + $(".messenger-list-item[data-contact=" + getMessengerId() + "]") + .find("tr>td>b") + .remove(); + // seen + $.ajax({ + url: url + "/makeSeen", + method: "POST", + data: { _token: csrfToken, id: getMessengerId() }, + dataType: "JSON", + }); + return clientSendChannel.trigger("client-seen", { + from_id: auth_id, // Me + to_id: getMessengerId(), // Messenger + seen: status, + }); } /** - *------------------------------------------------------------- - * Trigger contact item updates - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Trigger contact item updates +*------------------------------------------------------------- +*/ function sendContactItemUpdates(status) { - return clientSendChannel.trigger("client-contactItem", { - from: auth_id, // Me - to: getMessengerId(), // Messenger - update: status, - }); + return clientSendChannel.trigger("client-contactItem", { + from: auth_id, // Me + to: getMessengerId(), // Messenger + update: status, + }); } /** - *------------------------------------------------------------- - * Trigger message delete - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Trigger message delete +*------------------------------------------------------------- +*/ function sendMessageDeleteEvent(messageId) { - return clientSendChannel.trigger("client-messageDelete", { - id: messageId, - }); + return clientSendChannel.trigger("client-messageDelete", { + id: messageId, + }); } /** - *------------------------------------------------------------- - * Trigger delete conversation - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Trigger delete conversation +*------------------------------------------------------------- +*/ function sendDeleteConversationEvent() { - return clientSendChannel.trigger("client-deleteConversation", { - from: auth_id, - to: getMessengerId(), - }); + return clientSendChannel.trigger("client-deleteConversation", { + from: auth_id, + to: getMessengerId(), + }); } /** - *------------------------------------------------------------- - * Check internet connection using pusher states - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Check internet connection using pusher states +*------------------------------------------------------------- +*/ function checkInternet(state, selector) { - let net_errs = 0; - const messengerTitle = $(".messenger-headTitle"); - switch (state) { - case "connected": - if (net_errs < 1) { - messengerTitle.text(messengerTitleDefault); - selector.addClass("successBG-rgba"); - selector.find("span").hide(); - selector.slideDown("fast", function () { - selector.find(".ic-connected").show(); - }); - setTimeout(function () { - $(".internet-connection").slideUp("fast"); - }, 3000); - } - break; - case "connecting": - messengerTitle.text($(".ic-connecting").text()); - selector.removeClass("successBG-rgba"); - selector.find("span").hide(); - selector.slideDown("fast", function () { - selector.find(".ic-connecting").show(); - }); - net_errs = 1; - break; - // Not connected - default: - messengerTitle.text($(".ic-noInternet").text()); - selector.removeClass("successBG-rgba"); - selector.find("span").hide(); - selector.slideDown("fast", function () { - selector.find(".ic-noInternet").show(); - }); - net_errs = 1; - break; - } + let net_errs = 0; + const messengerTitle = $(".messenger-headTitle"); + switch (state) { + case "connected": + if (net_errs < 1) { + messengerTitle.text(messengerTitleDefault); + selector.addClass("successBG-rgba"); + selector.find("span").hide(); + selector.slideDown("fast", function () { + selector.find(".ic-connected").show(); + }); + setTimeout(function () { + $(".internet-connection").slideUp("fast"); + }, 3000); + } + break; + case "connecting": + messengerTitle.text($(".ic-connecting").text()); + selector.removeClass("successBG-rgba"); + selector.find("span").hide(); + selector.slideDown("fast", function () { + selector.find(".ic-connecting").show(); + }); + net_errs = 1; + break; + // Not connected + default: + messengerTitle.text($(".ic-noInternet").text()); + selector.removeClass("successBG-rgba"); + selector.find("span").hide(); + selector.slideDown("fast", function () { + selector.find(".ic-noInternet").show(); + }); + net_errs = 1; + break; + } } /** - *------------------------------------------------------------- - * Get contacts - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Get contacts +*------------------------------------------------------------- +*/ let contactsPage = 1; let contactsLoading = false; let noMoreContacts = false; function setContactsLoading(loading = false) { - if (!loading) { - $(".listOfContacts").find(".loading-contacts").remove(); - } else { - $(".listOfContacts").append( - `
${listItemLoading(4)}
` - ); - } - contactsLoading = loading; + if (!loading) { + $(".listOfContacts").find(".loading-contacts").remove(); + } else { + $(".listOfContacts").append( + `
${listItemLoading(4)}
` + ); + } + contactsLoading = loading; } function getContacts() { - if (!contactsLoading && !noMoreContacts) { - setContactsLoading(true); - $.ajax({ - url: url + "/getContacts", - method: "GET", - data: { _token: csrfToken, page: contactsPage }, - dataType: "JSON", - success: (data) => { - setContactsLoading(false); - if (contactsPage < 2) { - $(".listOfContacts").html(data.contacts); - } else { - $(".listOfContacts").append(data.contacts); - } - updateSelectedContact(); - // update data-action required with [responsive design] - cssMediaQueries(); - // Pagination lock & messages page - noMoreContacts = contactsPage >= data?.last_page; - if (!noMoreContacts) contactsPage += 1; - }, - error: (error) => { - setContactsLoading(false); - console.error(error); - }, - }); - } + if (!contactsLoading && !noMoreContacts) { + setContactsLoading(true); + $.ajax({ + url: url + "/getContacts", + method: "GET", + data: { _token: csrfToken, page: contactsPage }, + dataType: "JSON", + success: (data) => { + setContactsLoading(false); + if (contactsPage < 2) { + $(".listOfContacts").html(data.contacts); + } else { + $(".listOfContacts").append(data.contacts); + } + updateSelectedContact(); + // update data-action required with [responsive design] + cssMediaQueries(); + // Pagination lock & messages page + noMoreContacts = contactsPage >= data?.last_page; + if (!noMoreContacts) contactsPage += 1; + }, + error: (error) => { + setContactsLoading(false); + console.error(error); + }, + }); + } } /** - *------------------------------------------------------------- - * Update contact item - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Update contact item +*------------------------------------------------------------- +*/ function updateContactItem(user_id) { - if (user_id != auth_id) { - $.ajax({ - url: url + "/updateContacts", - method: "POST", - data: { - _token: csrfToken, - user_id, - }, - dataType: "JSON", - success: (data) => { - $(".listOfContacts") - .find(".messenger-list-item[data-contact=" + user_id + "]") - .remove(); - if (data.contactItem) $(".listOfContacts").prepend(data.contactItem); - if (user_id == getMessengerId()) updateSelectedContact(user_id); - // show/hide message hint (empty state message) - const totalContacts = - $(".listOfContacts").find(".messenger-list-item")?.length || 0; - if (totalContacts > 0) { - $(".listOfContacts").find(".message-hint").hide(); - } else { - $(".listOfContacts").find(".message-hint").show(); - } - // update data-action required with [responsive design] - cssMediaQueries(); - }, - error: (error) => { - console.error(error); - }, - }); - } + if (user_id != auth_id) { + $.ajax({ + url: url + "/updateContacts", + method: "POST", + data: { + _token: csrfToken, + user_id, + }, + dataType: "JSON", + success: (data) => { + $(".listOfContacts") + .find(".messenger-list-item[data-contact=" + user_id + "]") + .remove(); + if (data.contactItem) $(".listOfContacts").prepend(data.contactItem); + if (user_id == getMessengerId()) updateSelectedContact(user_id); + // show/hide message hint (empty state message) + const totalContacts = + $(".listOfContacts").find(".messenger-list-item")?.length || 0; + if (totalContacts > 0) { + $(".listOfContacts").find(".message-hint").hide(); + } else { + $(".listOfContacts").find(".message-hint").show(); + } + // update data-action required with [responsive design] + cssMediaQueries(); + }, + error: (error) => { + console.error(error); + }, + }); + } } /** - *------------------------------------------------------------- - * Star - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Star +*------------------------------------------------------------- +*/ function star(user_id) { - if (getMessengerId() != auth_id) { - $.ajax({ - url: url + "/star", - method: "POST", - data: { _token: csrfToken, user_id: user_id }, - dataType: "JSON", - success: (data) => { - data.status > 0 - ? $(".add-to-favorite").addClass("favorite") - : $(".add-to-favorite").removeClass("favorite"); - }, - error: () => { - console.error("Server error, check your response"); - }, - }); - } + if (getMessengerId() != auth_id) { + $.ajax({ + url: url + "/star", + method: "POST", + data: { _token: csrfToken, user_id: user_id }, + dataType: "JSON", + success: (data) => { + data.status > 0 + ? $(".add-to-favorite").addClass("favorite") + : $(".add-to-favorite").removeClass("favorite"); + }, + error: () => { + console.error("Server error, check your response"); + }, + }); + } } /** - *------------------------------------------------------------- - * Get favorite list - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Get favorite list +*------------------------------------------------------------- +*/ function getFavoritesList() { - $(".messenger-favorites").html(avatarLoading(4)); - $.ajax({ - url: url + "/favorites", - method: "POST", - data: { _token: csrfToken }, - dataType: "JSON", - success: (data) => { - if (data.count > 0) { - $(".favorites-section").show(); - $(".messenger-favorites").html(data.favorites); - } else { - $(".favorites-section").hide(); - } - // update data-action required with [responsive design] - cssMediaQueries(); - }, - error: () => { - console.error("Server error, check your response"); - }, - }); + $(".messenger-favorites").html(avatarLoading(4)); + $.ajax({ + url: url + "/favorites", + method: "POST", + data: { _token: csrfToken }, + dataType: "JSON", + success: (data) => { + if (data.count > 0) { + $(".favorites-section").show(); + $(".messenger-favorites").html(data.favorites); + } else { + $(".favorites-section").hide(); + } + // update data-action required with [responsive design] + cssMediaQueries(); + }, + error: () => { + console.error("Server error, check your response"); + }, + }); } /** - *------------------------------------------------------------- - * Get shared photos - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Get shared photos +*------------------------------------------------------------- +*/ function getSharedPhotos(user_id) { - $.ajax({ - url: url + "/shared", - method: "POST", - data: { _token: csrfToken, user_id: user_id }, - dataType: "JSON", - success: (data) => { - $(".shared-photos-list").html(data.shared); - }, - error: () => { - console.error("Server error, check your response"); - }, - }); + $.ajax({ + url: url + "/shared", + method: "POST", + data: { _token: csrfToken, user_id: user_id }, + dataType: "JSON", + success: (data) => { + $(".shared-photos-list").html(data.shared); + }, + error: () => { + console.error("Server error, check your response"); + }, + }); } /** - *------------------------------------------------------------- - * Search in messenger - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Search in messenger +*------------------------------------------------------------- +*/ let searchPage = 1; let noMoreDataSearch = false; let searchLoading = false; let searchTempVal = ""; function setSearchLoading(loading = false) { - if (!loading) { - $(".search-records").find(".loading-search").remove(); - } else { - $(".search-records").append( - `` - ); - } - searchLoading = loading; + if (!loading) { + $(".search-records").find(".loading-search").remove(); + } else { + $(".search-records").append( + `` + ); + } + searchLoading = loading; } function messengerSearch(input) { - if (input != searchTempVal) { - searchPage = 1; - noMoreDataSearch = false; - searchLoading = false; - } - searchTempVal = input; - if (!searchLoading && !noMoreDataSearch) { - if (searchPage < 2) { - $(".search-records").html(""); - } - setSearchLoading(true); - $.ajax({ - url: url + "/search", - method: "GET", - data: { _token: csrfToken, input: input, page: searchPage }, - dataType: "JSON", - success: (data) => { - setSearchLoading(false); - if (searchPage < 2) { - $(".search-records").html(data.records); - } else { - $(".search-records").append(data.records); - } - // update data-action required with [responsive design] - cssMediaQueries(); - // Pagination lock & messages page - noMoreDataSearch = searchPage >= data?.last_page; - if (!noMoreDataSearch) searchPage += 1; - }, - error: (error) => { - setSearchLoading(false); - console.error(error); - }, - }); - } + if (input != searchTempVal) { + searchPage = 1; + noMoreDataSearch = false; + searchLoading = false; + } + searchTempVal = input; + if (!searchLoading && !noMoreDataSearch) { + if (searchPage < 2) { + $(".search-records").html(""); + } + setSearchLoading(true); + $.ajax({ + url: url + "/search", + method: "GET", + data: { _token: csrfToken, input: input, page: searchPage }, + dataType: "JSON", + success: (data) => { + setSearchLoading(false); + if (searchPage < 2) { + $(".search-records").html(data.records); + } else { + $(".search-records").append(data.records); + } + // update data-action required with [responsive design] + cssMediaQueries(); + // Pagination lock & messages page + noMoreDataSearch = searchPage >= data?.last_page; + if (!noMoreDataSearch) searchPage += 1; + }, + error: (error) => { + setSearchLoading(false); + console.error(error); + }, + }); + } } /** - *------------------------------------------------------------- - * Delete Conversation - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Delete Conversation +*------------------------------------------------------------- +*/ function deleteConversation(id) { - $.ajax({ - url: url + "/deleteConversation", - method: "POST", - data: { _token: csrfToken, id: id }, - dataType: "JSON", - beforeSend: () => { - // hide delete modal - app_modal({ - show: false, - name: "delete", - }); - // Show waiting alert modal - app_modal({ - show: true, - name: "alert", - buttons: false, - body: loadingSVG("32px", null, "margin:auto"), - }); - }, - success: (data) => { - // delete contact from the list - $(".listOfContacts") - .find(".messenger-list-item[data-contact=" + id + "]") - .remove(); - // refresh info - IDinfo(id); - - if (!data.deleted) - return alert("Error occurred, messages can not be deleted!"); - - // Hide waiting alert modal - app_modal({ - show: false, - name: "alert", - buttons: true, - body: "", - }); - - sendDeleteConversationEvent(); - - // update contact list item - sendContactItemUpdates(true); - }, - error: () => { - console.error("Server error, check your response"); - }, - }); + $.ajax({ + url: url + "/deleteConversation", + method: "POST", + data: { _token: csrfToken, id: id }, + dataType: "JSON", + beforeSend: () => { + // hide delete modal + app_modal({ + show: false, + name: "delete", + }); + // Show waiting alert modal + app_modal({ + show: true, + name: "alert", + buttons: false, + body: loadingSVG("32px", null, "margin:auto"), + }); + }, + success: (data) => { + // delete contact from the list + $(".listOfContacts") + .find(".messenger-list-item[data-contact=" + id + "]") + .remove(); + // refresh info + IDinfo(id); + + if (!data.deleted) + return alert("Error occurred, messages can not be deleted!"); + + // Hide waiting alert modal + app_modal({ + show: false, + name: "alert", + buttons: true, + body: "", + }); + + sendDeleteConversationEvent(); + + // update contact list item + sendContactItemUpdates(true); + }, + error: () => { + console.error("Server error, check your response"); + }, + }); } /** - *------------------------------------------------------------- - * Delete Message By ID - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Delete Message By ID +*------------------------------------------------------------- +*/ function deleteMessage(id) { - $.ajax({ - url: url + "/deleteMessage", - method: "POST", - data: { _token: csrfToken, id: id }, - dataType: "JSON", - beforeSend: () => { - // hide delete modal - app_modal({ - show: false, - name: "delete", - }); - // Show waiting alert modal - app_modal({ - show: true, - name: "alert", - buttons: false, - body: loadingSVG("32px", null, "margin:auto"), - }); - }, - success: (data) => { - $(".messages").find(`.message-card[data-id=${id}]`).remove(); - if (!data.deleted) - console.error("Error occurred, message can not be deleted!"); - - sendMessageDeleteEvent(id); - - // Hide waiting alert modal - app_modal({ - show: false, - name: "alert", - buttons: true, - body: "", - }); - }, - error: () => { - console.error("Server error, check your response"); - }, - }); + $.ajax({ + url: url + "/deleteMessage", + method: "POST", + data: { _token: csrfToken, id: id }, + dataType: "JSON", + beforeSend: () => { + // hide delete modal + app_modal({ + show: false, + name: "delete", + }); + // Show waiting alert modal + app_modal({ + show: true, + name: "alert", + buttons: false, + body: loadingSVG("32px", null, "margin:auto"), + }); + }, + success: (data) => { + $(".messages").find(`.message-card[data-id=${id}]`).remove(); + if (!data.deleted) + console.error("Error occurred, message can not be deleted!"); + + sendMessageDeleteEvent(id); + + // Hide waiting alert modal + app_modal({ + show: false, + name: "alert", + buttons: true, + body: "", + }); + }, + error: () => { + console.error("Server error, check your response"); + }, + }); } /** - *------------------------------------------------------------- - * Update Settings - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Update Settings +*------------------------------------------------------------- +*/ function updateSettings() { - const formData = new FormData($("#update-settings")[0]); - if (messengerColor) { - formData.append("messengerColor", messengerColor); - } - if (dark_mode) { - formData.append("dark_mode", dark_mode); - } - $.ajax({ - url: url + "/updateSettings", - method: "POST", - data: formData, - dataType: "JSON", - processData: false, - contentType: false, - beforeSend: () => { - // close settings modal - app_modal({ - show: false, - name: "settings", - }); - // Show waiting alert modal - app_modal({ - show: true, - name: "alert", - buttons: false, - body: loadingSVG("32px", null, "margin:auto"), - }); - }, - success: (data) => { - if (data.error) { - // Show error message in alert modal - app_modal({ - show: true, - name: "alert", - buttons: true, - body: data.msg, - }); - } else { - // Hide alert modal - app_modal({ - show: false, - name: "alert", - buttons: true, - body: "", - }); - - // reload the page - location.reload(true); - } - }, - error: () => { - console.error("Server error, check your response"); - }, - }); + const formData = new FormData($("#update-settings")[0]); + if (messengerColor) { + formData.append("messengerColor", messengerColor); + } + if (dark_mode) { + formData.append("dark_mode", dark_mode); + } + $.ajax({ + url: url + "/updateSettings", + method: "POST", + data: formData, + dataType: "JSON", + processData: false, + contentType: false, + beforeSend: () => { + // close settings modal + app_modal({ + show: false, + name: "settings", + }); + // Show waiting alert modal + app_modal({ + show: true, + name: "alert", + buttons: false, + body: loadingSVG("32px", null, "margin:auto"), + }); + }, + success: (data) => { + if (data.error) { + // Show error message in alert modal + app_modal({ + show: true, + name: "alert", + buttons: true, + body: data.msg, + }); + } else { + // Hide alert modal + app_modal({ + show: false, + name: "alert", + buttons: true, + body: "", + }); + + // reload the page + location.reload(true); + } + }, + error: () => { + console.error("Server error, check your response"); + }, + }); } /** - *------------------------------------------------------------- - * Set Active status - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Set Active status +*------------------------------------------------------------- +*/ function setActiveStatus(status) { - $.ajax({ - url: url + "/setActiveStatus", - method: "POST", - data: { _token: csrfToken, status: status }, - dataType: "JSON", - success: (data) => { - // Nothing to do - }, - error: () => { - console.error("Server error, check your response"); - }, - }); + $.ajax({ + url: url + "/setActiveStatus", + method: "POST", + data: { _token: csrfToken, status: status }, + dataType: "JSON", + success: (data) => { + // Nothing to do + }, + error: () => { + console.error("Server error, check your response"); + }, + }); } function initializeUserConversationFromURL() { @@ -1267,380 +1286,380 @@ function initializeUserConversationFromURL() { } /** - *------------------------------------------------------------- - * On DOM ready - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* On DOM ready +*------------------------------------------------------------- +*/ $(document).ready(function () { - // get contacts list - getContacts(); - - // get contacts list - getFavoritesList(); - - // Clear typing timeout - clearTimeout(typingTimeout); - - // NProgress configurations - NProgress.configure({ showSpinner: false, minimum: 0.7, speed: 500 }); - - // make message input autosize. - autosize($(".m-send")); - - // check if pusher has access to the channel [Internet status] - pusher.connection.bind("state_change", function (states) { - let selector = $(".internet-connection"); - checkInternet(states.current, selector); - // listening for pusher:subscription_succeeded - channel.bind("pusher:subscription_succeeded", function () { - // On connection state change [Updating] and get [info & msgs] - if (getMessengerId() != 0) { - if ( - $(".messenger-list-item") - .find("tr[data-action]") - .attr("data-action") == "1" - ) { - $(".messenger-listView").hide(); - } - IDinfo(getMessengerId()); - } - }); - }); - - // tabs on click, show/hide... - $(".messenger-listView-tabs a").on("click", function () { - var dataView = $(this).attr("data-view"); - $(".messenger-listView-tabs a").removeClass("active-tab"); - $(this).addClass("active-tab"); - $(".messenger-tab").hide(); - $(".messenger-tab[data-view=" + dataView + "]").show(); - }); - - // set item active on click - $("body").on("click", ".messenger-list-item", function () { - $(".messenger-list-item").removeClass("m-list-active"); - $(this).addClass("m-list-active"); - const userID = $(this).attr("data-contact"); - routerPush(document.title, `${url}/${userID}`); - updateSelectedContact(userID); - }); - - // show info side button - $(".messenger-infoView nav a , .show-infoSide").on("click", function () { - $(".messenger-infoView").toggle(); - }); - - // make favorites card dragable on click to slide. - hScroller(".messenger-favorites"); - - // click action for list item [user/group] - $("body").on("click", ".messenger-list-item", function () { - if ($(this).find("tr[data-action]").attr("data-action") == "1") { - $(".messenger-listView").hide(); - } - const dataId = $(this).find("p[data-id]").attr("data-id"); - setMessengerId(dataId); - // IDinfo(dataId); - }); - - // click action for favorite button - $("body").on("click", ".favorite-list-item", function () { - if ($(this).find("div").attr("data-action") == "1") { - $(".messenger-listView").hide(); - } - const uid = $(this).find("div.avatar").attr("data-id"); - setMessengerId(uid); - IDinfo(uid); - updateSelectedContact(uid); - routerPush(document.title, `${url}/${uid}`); - }); - - // list view buttons - $(".listView-x").on("click", function () { - $(".messenger-listView").hide(); - }); - $(".show-listView").on("click", function () { - routerPush(document.title, `${url}/`); - $(".messenger-listView").show(); - }); - - // click action for [add to favorite] button. - $(".add-to-favorite").on("click", function () { - star(getMessengerId()); - }); - - // calling Css Media Queries - cssMediaQueries(); - - // message form on submit. - $("#message-form").on("submit", (e) => { - e.preventDefault(); - sendMessage(); - }); - - // message input on keyup [Enter to send, Enter+Shift for new line] - $("#message-form .m-send").on("keyup", (e) => { - // if enter key pressed. - if (e.which == 13 || e.keyCode == 13) { - // if shift + enter key pressed, do nothing (new line). - // if only enter key pressed, send message. - if (!e.shiftKey) { - triggered = isTyping(false); - sendMessage(); - } - } - }); - - // On [upload attachment] input change, show a preview of the image/file. - $("body").on("change", ".upload-attachment", (e) => { - let file = e.target.files[0]; - if (!attachmentValidate(file)) return false; - let reader = new FileReader(); - let sendCard = $(".messenger-sendCard"); - reader.readAsDataURL(file); - reader.addEventListener("loadstart", (e) => { - $("#message-form").before(loadingSVG()); - }); - reader.addEventListener("load", (e) => { - $(".messenger-sendCard").find(".loadingSVG").remove(); - if (!file.type.match("image.*")) { - // if the file not image - sendCard.find(".attachment-preview").remove(); // older one - sendCard.prepend(attachmentTemplate("file", file.name)); - } else { - // if the file is an image - sendCard.find(".attachment-preview").remove(); // older one - sendCard.prepend( - attachmentTemplate("image", file.name, e.target.result) - ); - } - }); - }); - - function attachmentValidate(file) { - const fileElement = $(".upload-attachment"); - const { name: fileName, size: fileSize } = file; - const fileExtension = fileName.split(".").pop(); - if ( - !chatify.allAllowedExtensions.includes( - fileExtension.toString().toLowerCase() - ) - ) { - alert("file type not allowed"); - fileElement.val(""); - return false; - } - // Validate file size. - if (fileSize > chatify.maxUploadSize) { - alert("File is too large!"); - return false; - } - return true; - } - - // Attachment preview cancel button. - $("body").on("click", ".attachment-preview .cancel", () => { - cancelAttachment(); - }); - - // typing indicator on [input] keyDown - $("#message-form .m-send").on("keydown", () => { - if (typingNow < 1) { - isTyping(true); - typingNow = 1; - } - clearTimeout(typingTimeout); - typingTimeout = setTimeout(function () { - isTyping(false); - typingNow = 0; - }, 1000); - }); - - // Image modal - $("body").on("click", ".chat-image", function () { - let src = $(this).css("background-image").split(/"/)[1]; - $("#imageModalBox").show(); - $("#imageModalBoxSrc").attr("src", src); - }); - $(".imageModal-close").on("click", function () { - $("#imageModalBox").hide(); - }); - - // Search input on focus - $(".messenger-search").on("focus", function () { - $(".messenger-tab").hide(); - $('.messenger-tab[data-view="search"]').show(); - }); - $(".messenger-search").on("blur", function () { - setTimeout(function () { - $(".messenger-tab").hide(); - $('.messenger-tab[data-view="users"]').show(); - }, 200); - }); - // Search action on keyup - const debouncedSearch = debounce(function () { - const value = $(".messenger-search").val(); - messengerSearch(value); - }, 500); - $(".messenger-search").on("keyup", function (e) { - const value = $(this).val(); - if ($.trim(value).length > 0) { - $(".messenger-search").trigger("focus"); - debouncedSearch(); - } else { - $(".messenger-tab").hide(); - $('.messenger-listView-tabs a[data-view="users"]').trigger("click"); - } - }); - - // Delete Conversation button - $(".messenger-infoView-btns .delete-conversation").on("click", function () { - app_modal({ - name: "delete", - }); - }); - // Delete Message Button - $("body").on("click", ".message-card .actions .delete-btn", function () { - app_modal({ - name: "delete", - data: $(this).data("id"), - }); - }); - // Delete modal [on delete button click] - $(".app-modal[data-name=delete]") - .find(".app-modal-footer .delete") - .on("click", function () { - const id = $("body") - .find(".app-modal[data-name=delete]") - .find(".app-modal-card") - .attr("data-modal"); - if (id == 0) { - deleteConversation(getMessengerId()); - } else { - deleteMessage(id); - } - app_modal({ - show: false, - name: "delete", - }); - }); - // delete modal [cancel button] - $(".app-modal[data-name=delete]") - .find(".app-modal-footer .cancel") - .on("click", function () { - app_modal({ - show: false, - name: "delete", - }); - }); - - // Settings button action to show settings modal - $("body").on("click", ".settings-btn", function (e) { - e.preventDefault(); - app_modal({ - show: true, - name: "settings", - }); - }); - - // on submit settings' form - $("#update-settings").on("submit", (e) => { - e.preventDefault(); - updateSettings(); - }); - // Settings modal [cancel button] - $(".app-modal[data-name=settings]") - .find(".app-modal-footer .cancel") - .on("click", function () { - app_modal({ - show: false, - name: "settings", - }); - cancelUpdatingAvatar(); - }); - // upload avatar on change - $("body").on("change", ".upload-avatar", (e) => { - // store the original avatar - if (defaultAvatarInSettings == null) { - defaultAvatarInSettings = $(".upload-avatar-preview").css( - "background-image" - ); - } - let file = e.target.files[0]; - if (!attachmentValidate(file)) return false; - let reader = new FileReader(); - reader.readAsDataURL(file); - reader.addEventListener("loadstart", (e) => { - $(".upload-avatar-preview").append( - loadingSVG("42px", "upload-avatar-loading") - ); - }); - reader.addEventListener("load", (e) => { - $(".upload-avatar-preview").find(".loadingSVG").remove(); - if (!file.type.match("image.*")) { - // if the file is not an image - console.error("File you selected is not an image!"); - } else { - // if the file is an image - $(".upload-avatar-preview").css( - "background-image", - 'url("' + e.target.result + '")' - ); - } - }); - }); - // change messenger color button - $("body").on("click", ".update-messengerColor .color-btn", function () { - messengerColor = $(this).attr("data-color"); - $(".update-messengerColor .color-btn").removeClass("m-color-active"); - $(this).addClass("m-color-active"); - }); - // Switch to Dark/Light mode - $("body").on("click", ".dark-mode-switch", function () { - if ($(this).attr("data-mode") == "0") { - $(this).attr("data-mode", "1"); - $(this).removeClass("far"); - $(this).addClass("fas"); - dark_mode = "dark"; - } else { - $(this).attr("data-mode", "0"); - $(this).removeClass("fas"); - $(this).addClass("far"); - dark_mode = "light"; - } - }); - - //Messages pagination - actionOnScroll( - ".m-body.messages-container", - function () { - fetchMessages(getMessengerId()); - }, - true - ); - //Contacts pagination - actionOnScroll(".messenger-tab.users-tab", function () { - getContacts(); - }); - //Search pagination - actionOnScroll(".messenger-tab.search-tab", function () { - messengerSearch($(".messenger-search").val()); - }); + // get contacts list + getContacts(); + + // get contacts list + getFavoritesList(); + + // Clear typing timeout + clearTimeout(typingTimeout); + + // NProgress configurations + NProgress.configure({ showSpinner: false, minimum: 0.7, speed: 500 }); + + // make message input autosize. + autosize($(".m-send")); + + // check if pusher has access to the channel [Internet status] + pusher.connection.bind("state_change", function (states) { + let selector = $(".internet-connection"); + checkInternet(states.current, selector); + // listening for pusher:subscription_succeeded + channel.bind("pusher:subscription_succeeded", function () { + // On connection state change [Updating] and get [info & msgs] + if (getMessengerId() != 0) { + if ( + $(".messenger-list-item") + .find("tr[data-action]") + .attr("data-action") == "1" + ) { + $(".messenger-listView").hide(); + } + IDinfo(getMessengerId()); + } + }); + }); + + // tabs on click, show/hide... + $(".messenger-listView-tabs a").on("click", function () { + var dataView = $(this).attr("data-view"); + $(".messenger-listView-tabs a").removeClass("active-tab"); + $(this).addClass("active-tab"); + $(".messenger-tab").hide(); + $(".messenger-tab[data-view=" + dataView + "]").show(); + }); + + // set item active on click + $("body").on("click", ".messenger-list-item", function () { + $(".messenger-list-item").removeClass("m-list-active"); + $(this).addClass("m-list-active"); + const userID = $(this).attr("data-contact"); + routerPush(document.title, `${url}/${userID}`); + updateSelectedContact(userID); + }); + + // show info side button + $(".messenger-infoView nav a , .show-infoSide").on("click", function () { + $(".messenger-infoView").toggle(); + }); + + // make favorites card dragable on click to slide. + hScroller(".messenger-favorites"); + + // click action for list item [user/group] + $("body").on("click", ".messenger-list-item", function () { + if ($(this).find("tr[data-action]").attr("data-action") == "1") { + $(".messenger-listView").hide(); + } + const dataId = $(this).find("p[data-id]").attr("data-id"); + setMessengerId(dataId); + // IDinfo(dataId); + }); + + // click action for favorite button + $("body").on("click", ".favorite-list-item", function () { + if ($(this).find("div").attr("data-action") == "1") { + $(".messenger-listView").hide(); + } + const uid = $(this).find("div.avatar").attr("data-id"); + setMessengerId(uid); + IDinfo(uid); + updateSelectedContact(uid); + routerPush(document.title, `${url}/${uid}`); + }); + + // list view buttons + $(".listView-x").on("click", function () { + $(".messenger-listView").hide(); + }); + $(".show-listView").on("click", function () { + routerPush(document.title, `${url}/`); + $(".messenger-listView").show(); + }); + + // click action for [add to favorite] button. + $(".add-to-favorite").on("click", function () { + star(getMessengerId()); + }); + + // calling Css Media Queries + cssMediaQueries(); + + // message form on submit. + $("#message-form").on("submit", (e) => { + e.preventDefault(); + sendMessage(); + }); + + // message input on keyup [Enter to send, Enter+Shift for new line] + $("#message-form .m-send").on("keyup", (e) => { + // if enter key pressed. + if (e.which == 13 || e.keyCode == 13) { + // if shift + enter key pressed, do nothing (new line). + // if only enter key pressed, send message. + if (!e.shiftKey) { + triggered = isTyping(false); + sendMessage(); + } + } + }); + + // On [upload attachment] input change, show a preview of the image/file. + $("body").on("change", ".upload-attachment", (e) => { + let file = e.target.files[0]; + if (!attachmentValidate(file)) return false; + let reader = new FileReader(); + let sendCard = $(".messenger-sendCard"); + reader.readAsDataURL(file); + reader.addEventListener("loadstart", (e) => { + $("#message-form").before(loadingSVG()); + }); + reader.addEventListener("load", (e) => { + $(".messenger-sendCard").find(".loadingSVG").remove(); + if (!file.type.match("image.*")) { + // if the file not image + sendCard.find(".attachment-preview").remove(); // older one + sendCard.prepend(attachmentTemplate("file", file.name)); + } else { + // if the file is an image + sendCard.find(".attachment-preview").remove(); // older one + sendCard.prepend( + attachmentTemplate("image", file.name, e.target.result) + ); + } + }); + }); + + function attachmentValidate(file) { + const fileElement = $(".upload-attachment"); + const { name: fileName, size: fileSize } = file; + const fileExtension = fileName.split(".").pop(); + if ( + !chatify.allAllowedExtensions.includes( + fileExtension.toString().toLowerCase() + ) + ) { + alert("file type not allowed"); + fileElement.val(""); + return false; + } + // Validate file size. + if (fileSize > chatify.maxUploadSize) { + alert("File is too large!"); + return false; + } + return true; + } + + // Attachment preview cancel button. + $("body").on("click", ".attachment-preview .cancel", () => { + cancelAttachment(); + }); + + // typing indicator on [input] keyDown + $("#message-form .m-send").on("keydown", () => { + if (typingNow < 1) { + isTyping(true); + typingNow = 1; + } + clearTimeout(typingTimeout); + typingTimeout = setTimeout(function () { + isTyping(false); + typingNow = 0; + }, 1000); + }); + + // Image modal + $("body").on("click", ".chat-image", function () { + let src = $(this).css("background-image").split(/"/)[1]; + $("#imageModalBox").show(); + $("#imageModalBoxSrc").attr("src", src); + }); + $(".imageModal-close").on("click", function () { + $("#imageModalBox").hide(); + }); + + // Search input on focus + $(".messenger-search").on("focus", function () { + $(".messenger-tab").hide(); + $('.messenger-tab[data-view="search"]').show(); + }); + $(".messenger-search").on("blur", function () { + setTimeout(function () { + $(".messenger-tab").hide(); + $('.messenger-tab[data-view="users"]').show(); + }, 200); + }); + // Search action on keyup + const debouncedSearch = debounce(function () { + const value = $(".messenger-search").val(); + messengerSearch(value); + }, 500); + $(".messenger-search").on("keyup", function (e) { + const value = $(this).val(); + if ($.trim(value).length > 0) { + $(".messenger-search").trigger("focus"); + debouncedSearch(); + } else { + $(".messenger-tab").hide(); + $('.messenger-listView-tabs a[data-view="users"]').trigger("click"); + } + }); + + // Delete Conversation button + $(".messenger-infoView-btns .delete-conversation").on("click", function () { + app_modal({ + name: "delete", + }); + }); + // Delete Message Button + $("body").on("click", ".message-card .actions .delete-btn", function () { + app_modal({ + name: "delete", + data: $(this).data("id"), + }); + }); + // Delete modal [on delete button click] + $(".app-modal[data-name=delete]") + .find(".app-modal-footer .delete") + .on("click", function () { + const id = $("body") + .find(".app-modal[data-name=delete]") + .find(".app-modal-card") + .attr("data-modal"); + if (id == 0) { + deleteConversation(getMessengerId()); + } else { + deleteMessage(id); + } + app_modal({ + show: false, + name: "delete", + }); + }); + // delete modal [cancel button] + $(".app-modal[data-name=delete]") + .find(".app-modal-footer .cancel") + .on("click", function () { + app_modal({ + show: false, + name: "delete", + }); + }); + + // Settings button action to show settings modal + $("body").on("click", ".settings-btn", function (e) { + e.preventDefault(); + app_modal({ + show: true, + name: "settings", + }); + }); + + // on submit settings' form + $("#update-settings").on("submit", (e) => { + e.preventDefault(); + updateSettings(); + }); + // Settings modal [cancel button] + $(".app-modal[data-name=settings]") + .find(".app-modal-footer .cancel") + .on("click", function () { + app_modal({ + show: false, + name: "settings", + }); + cancelUpdatingAvatar(); + }); + // upload avatar on change + $("body").on("change", ".upload-avatar", (e) => { + // store the original avatar + if (defaultAvatarInSettings == null) { + defaultAvatarInSettings = $(".upload-avatar-preview").css( + "background-image" + ); + } + let file = e.target.files[0]; + if (!attachmentValidate(file)) return false; + let reader = new FileReader(); + reader.readAsDataURL(file); + reader.addEventListener("loadstart", (e) => { + $(".upload-avatar-preview").append( + loadingSVG("42px", "upload-avatar-loading") + ); + }); + reader.addEventListener("load", (e) => { + $(".upload-avatar-preview").find(".loadingSVG").remove(); + if (!file.type.match("image.*")) { + // if the file is not an image + console.error("File you selected is not an image!"); + } else { + // if the file is an image + $(".upload-avatar-preview").css( + "background-image", + 'url("' + e.target.result + '")' + ); + } + }); + }); + // change messenger color button + $("body").on("click", ".update-messengerColor .color-btn", function () { + messengerColor = $(this).attr("data-color"); + $(".update-messengerColor .color-btn").removeClass("m-color-active"); + $(this).addClass("m-color-active"); + }); + // Switch to Dark/Light mode + $("body").on("click", ".dark-mode-switch", function () { + if ($(this).attr("data-mode") == "0") { + $(this).attr("data-mode", "1"); + $(this).removeClass("far"); + $(this).addClass("fas"); + dark_mode = "dark"; + } else { + $(this).attr("data-mode", "0"); + $(this).removeClass("fas"); + $(this).addClass("far"); + dark_mode = "light"; + } + }); + + //Messages pagination + actionOnScroll( + ".m-body.messages-container", + function () { + fetchMessages(getMessengerId()); + }, + true + ); + //Contacts pagination + actionOnScroll(".messenger-tab.users-tab", function () { + getContacts(); + }); + //Search pagination + actionOnScroll(".messenger-tab.search-tab", function () { + messengerSearch($(".messenger-search").val()); + }); initializeUserConversationFromURL(); }); /** - *------------------------------------------------------------- - * Observer on DOM changes - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Observer on DOM changes +*------------------------------------------------------------- +*/ let previousMessengerId = getMessengerId(); const observer = new MutationObserver(function (mutations) { - if (getMessengerId() !== previousMessengerId) { - previousMessengerId = getMessengerId(); - initClientChannel(); - } + if (getMessengerId() !== previousMessengerId) { + previousMessengerId = getMessengerId(); + initClientChannel(); + } }); const config = { subtree: true, childList: true }; @@ -1651,84 +1670,84 @@ observer.observe(document, config); // observer.disconnect(); /** - *------------------------------------------------------------- - * Resize messaging area when resize the viewport. - * on mobile devices when the keyboard is shown, the viewport - * height is changed, so we need to resize the messaging area - * to fit the new height. - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Resize messaging area when resize the viewport. +* on mobile devices when the keyboard is shown, the viewport +* height is changed, so we need to resize the messaging area +* to fit the new height. +*------------------------------------------------------------- +*/ var resizeTimeout; window.visualViewport.addEventListener("resize", (e) => { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(function () { - const h = e.target.height; - if (h) { - $(".messenger-messagingView").css({ height: h + "px" }); - } - }, 100); + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(function () { + const h = e.target.height; + if (h) { + $(".messenger-messagingView").css({ height: h + "px" }); + } + }, 100); }); /** - *------------------------------------------------------------- - * Emoji Picker - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Emoji Picker +*------------------------------------------------------------- +*/ const emojiButton = document.querySelector(".emoji-button"); const emojiPicker = new EmojiButton({ - theme: messengerTheme, - autoHide: false, - position: "top-start", + theme: messengerTheme, + autoHide: false, + position: "top-start", }); emojiButton.addEventListener("click", (e) => { - e.preventDefault(); - emojiPicker.togglePicker(emojiButton); + e.preventDefault(); + emojiPicker.togglePicker(emojiButton); }); emojiPicker.on("emoji", (emoji) => { - const el = messageInput[0]; - const startPos = el.selectionStart; - const endPos = el.selectionEnd; - const value = messageInput.val(); - const newValue = - value.substring(0, startPos) + - emoji + - value.substring(endPos, value.length); - messageInput.val(newValue); - el.selectionStart = el.selectionEnd = startPos + emoji.length; - el.focus(); + const el = messageInput[0]; + const startPos = el.selectionStart; + const endPos = el.selectionEnd; + const value = messageInput.val(); + const newValue = + value.substring(0, startPos) + + emoji + + value.substring(endPos, value.length); + messageInput.val(newValue); + el.selectionStart = el.selectionEnd = startPos + emoji.length; + el.focus(); }); /** - *------------------------------------------------------------- - * Notification sounds - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Notification sounds +*------------------------------------------------------------- +*/ function playNotificationSound(soundName, condition = false) { - if ((document.hidden || condition) && chatify.sounds.enabled) { - const sound = new Audio( - `/${chatify.sounds.public_path}/${chatify.sounds[soundName]}` - ); - sound.play(); - } + if ((document.hidden || condition) && chatify.sounds.enabled) { + const sound = new Audio( + `/${chatify.sounds.public_path}/${chatify.sounds[soundName]}` + ); + sound.play(); + } } /** - *------------------------------------------------------------- - * Update and format dates to time ago. - *------------------------------------------------------------- - */ +*------------------------------------------------------------- +* Update and format dates to time ago. +*------------------------------------------------------------- +*/ function updateElementsDateToTimeAgo() { - $(".message-time").each(function () { - const time = $(this).attr("data-time"); - $(this).find(".time").text(dateStringToTimeAgo(time)); - }); - $(".contact-item-time").each(function () { - const time = $(this).attr("data-time"); - $(this).text(dateStringToTimeAgo(time)); - }); + $(".message-time").each(function () { + const time = $(this).attr("data-time"); + $(this).find(".time").text(dateStringToTimeAgo(time)); + }); + $(".contact-item-time").each(function () { + const time = $(this).attr("data-time"); + $(this).text(dateStringToTimeAgo(time)); + }); } setInterval(() => { - updateElementsDateToTimeAgo(); + updateElementsDateToTimeAgo(); }, 60000); diff --git a/src/assets/js/voiceMessage.js b/src/assets/js/voiceMessage.js new file mode 100644 index 00000000..0a8555c6 --- /dev/null +++ b/src/assets/js/voiceMessage.js @@ -0,0 +1,364 @@ +/* +**************************************************************************** +* Recording Voice Message +**************************************************************************** +*/ +(function () { + const MAX_RECORDING_TIME = 60; + const AUDIO_TYPES = [ + { mimeType: "audio/webm;codecs=opus", extension: "webm" }, + { mimeType: "audio/webm", extension: "webm" }, + { mimeType: "audio/mp4", extension: "mp4" }, + { mimeType: "audio/mpeg", extension: "mp3" }, + ]; + + let microphoneButton; + let form; + let recordingUI; + let trashButton; + let recordingProgressBar; + let recordingTime; + let stopButton; + let sendRecordButton; + + let isRecording = false; + let isPaused = false; + let isStopping = false; + let mediaRecorder = null; + let audioChunks = []; + let stream = null; + let recordingInterval = null; + let recordingSeconds = 0; + let recorderType = null; + + function initializeDOMElements() { + microphoneButton = document.getElementById("microphone-button"); + form = document.getElementById("message-form"); + recordingUI = document.getElementById("recording-ui"); + trashButton = document.getElementById("trash-button"); + recordingProgressBar = document.getElementById("recording-progress-bar"); + recordingTime = document.getElementById("recording-time"); + stopButton = document.getElementById("stop-button"); + sendRecordButton = document.getElementById("send-record-button"); + + return [ + microphoneButton, + form, + recordingUI, + trashButton, + recordingProgressBar, + recordingTime, + stopButton, + sendRecordButton, + ].every(Boolean); + } + + function canRecordAudio() { + return !!(navigator.mediaDevices?.getUserMedia && window.MediaRecorder); + } + + function getSupportedAudioType() { + if (!window.MediaRecorder?.isTypeSupported) { + return { mimeType: "", extension: "webm" }; + } + + return AUDIO_TYPES.find((type) => MediaRecorder.isTypeSupported(type.mimeType)); + } + + function setupEventListeners() { + microphoneButton.addEventListener("click", toggleRecording); + stopButton.addEventListener("click", togglePauseResume); + sendRecordButton.addEventListener("click", sendRecording); + trashButton.addEventListener("click", cancelRecording); + } + + function toggleRecording(event) { + event.preventDefault(); + + if (isRecording || isPaused) { + cancelRecording(event); + return; + } + + requestMicrophonePermission(); + } + + function requestMicrophonePermission() { + if (!canRecordAudio()) { + alert("Voice recording is not supported in this browser."); + return; + } + + recorderType = getSupportedAudioType(); + + if (!recorderType) { + alert("Voice recording is not supported in this browser."); + return; + } + + navigator.mediaDevices + .getUserMedia({ audio: true }) + .then((audioStream) => { + stream = audioStream; + startRecording(); + }) + .catch((error) => { + console.error("Error accessing microphone:", error); + alert("Microphone permission is required to record audio."); + }); + } + + function startRecording() { + audioChunks = []; + recordingSeconds = 0; + isRecording = true; + isPaused = false; + isStopping = false; + + form.style.display = "none"; + recordingUI.style.display = "flex"; + microphoneButton.classList.add("recording"); + stopButton.innerHTML = ''; + + updateRecordingTime(); + updateProgressBar(); + + const options = recorderType.mimeType ? { mimeType: recorderType.mimeType } : undefined; + mediaRecorder = new MediaRecorder(stream, options); + + mediaRecorder.addEventListener("dataavailable", (event) => { + if (event.data?.size > 0) { + audioChunks.push(event.data); + } + }); + + mediaRecorder.addEventListener("stop", () => { + stopStream(); + isRecording = false; + isPaused = false; + isStopping = false; + microphoneButton.classList.remove("recording"); + stopButton.innerHTML = ''; + }); + + mediaRecorder.start(); + startRecordingTimer(); + } + + function startRecordingTimer() { + clearInterval(recordingInterval); + recordingInterval = setInterval(() => { + if (isPaused) { + return; + } + + recordingSeconds += 1; + updateRecordingTime(); + updateProgressBar(); + + if (recordingSeconds >= MAX_RECORDING_TIME) { + finishRecording(); + } + }, 1000); + } + + function finishRecording(callback) { + clearInterval(recordingInterval); + + if (!mediaRecorder || mediaRecorder.state === "inactive" || isStopping) { + if (typeof callback === "function") { + callback(); + } + return; + } + + isStopping = true; + mediaRecorder.addEventListener( + "stop", + () => { + if (typeof callback === "function") { + callback(); + } + }, + { once: true } + ); + mediaRecorder.stop(); + } + + function togglePauseResume(event) { + event.preventDefault(); + + if (!mediaRecorder || !isRecording) { + return; + } + + if (isPaused) { + mediaRecorder.resume(); + isPaused = false; + stopButton.innerHTML = ''; + return; + } + + mediaRecorder.pause(); + isPaused = true; + stopButton.innerHTML = ''; + } + + function sendRecording(event) { + event.preventDefault(); + + const duration = formatTime(recordingSeconds); + + finishRecording(() => { + const audioBlob = new Blob(audioChunks, { + type: recorderType?.mimeType || "audio/webm", + }); + + if (audioBlob.size > 0) { + sendMessage(true, audioBlob, duration, `voice-message.${recorderType?.extension || "webm"}`); + } + + resetRecordingState(); + }); + } + + function cancelRecording(event) { + event?.preventDefault(); + finishRecording(resetRecordingState); + } + + function resetRecordingState() { + clearInterval(recordingInterval); + audioChunks = []; + recordingSeconds = 0; + isRecording = false; + isPaused = false; + isStopping = false; + mediaRecorder = null; + + updateRecordingTime(); + updateProgressBar(); + stopStream(); + + form.style.display = "flex"; + recordingUI.style.display = "none"; + microphoneButton.classList.remove("recording"); + stopButton.innerHTML = ''; + } + + function stopStream() { + if (stream) { + stream.getTracks().forEach((track) => track.stop()); + stream = null; + } + } + + function updateRecordingTime() { + if (recordingTime) { + recordingTime.textContent = formatTime(recordingSeconds); + } + } + + function updateProgressBar() { + if (recordingProgressBar) { + recordingProgressBar.style.width = `${(recordingSeconds / MAX_RECORDING_TIME) * 100}%`; + } + } + + function formatTime(time) { + const minutes = Math.floor(time / 60); + const seconds = Math.floor(time % 60); + return `${minutes}:${seconds.toString().padStart(2, "0")}`; + } + + function setPlayIcon(playButton, isPlaying) { + const icon = playButton?.querySelector("i, svg"); + + if (!icon) { + return; + } + + icon.classList.toggle("fa-play", !isPlaying); + icon.classList.toggle("fa-pause", isPlaying); + icon.setAttribute("data-icon", isPlaying ? "pause" : "play"); + } + + function initializeAudioPlayer(playerId) { + const waveform = document.getElementById(`waveform-${playerId}`); + const playButton = document.querySelector(`.btn-toggle-play[data-player-id="${playerId}"]`); + + if (!waveform || !playButton || waveform.classList.contains("initialized")) { + return; + } + + if (typeof WaveSurfer === "undefined") { + console.error("WaveSurfer is not loaded."); + return; + } + + const wavesurfer = WaveSurfer.create({ + container: waveform, + waveColor: "rgb(218, 210, 210)", + progressColor: "grey", + cursorColor: "transparent", + barWidth: 2, + barRadius: 3, + height: 36, + responsive: true, + barGap: 3, + hideScrollbar: true, + }); + + waveform.classList.add("initialized"); + wavesurfer.load(waveform.getAttribute("data-audio-url")); + + playButton.addEventListener("click", function () { + wavesurfer.playPause(); + }); + + wavesurfer.on("play", function () { + setPlayIcon(playButton, true); + }); + + wavesurfer.on("pause", function () { + setPlayIcon(playButton, false); + }); + + wavesurfer.on("finish", function () { + setPlayIcon(playButton, false); + wavesurfer.seekTo(0); + }); + + wavesurfer.on("ready", function () { + const durationElement = document.querySelector(`#player-${playerId} .duration`); + + if (durationElement) { + durationElement.textContent = formatTime(wavesurfer.getDuration()); + } + }); + } + + function renderWavesurfers() { + document.querySelectorAll(".waveform").forEach((waveform) => { + initializeAudioPlayer(waveform.getAttribute("data-audio-id")); + }); + } + + document.addEventListener("DOMContentLoaded", function () { + if (!initializeDOMElements()) { + return; + } + + if (!canRecordAudio() || !getSupportedAudioType()) { + microphoneButton.setAttribute("disabled", "disabled"); + microphoneButton.classList.add("btn-disabled"); + return; + } + + setupEventListeners(); + }); + + window.initializeAudioPlayer = initializeAudioPlayer; + window.renderWavesurfers = renderWavesurfers; + window.rederWavesurfers = renderWavesurfers; +})(); diff --git a/src/config/chatify.php b/src/config/chatify.php index 359c93b7..e28c5293 100644 --- a/src/config/chatify.php +++ b/src/config/chatify.php @@ -87,6 +87,7 @@ 'download_route_name' => 'attachments.download', 'allowed_images' => (array) ['png','jpg','jpeg','gif'], 'allowed_files' => (array) ['zip','rar','txt'], + 'allowed_voice_messages' => (array) ['mpeg','mpga','mp3','wav','webm','mp4'], 'max_upload_size' => env('CHATIFY_MAX_FILE_SIZE', 150), // MB ], diff --git a/src/views/layouts/footerLinks.blade.php b/src/views/layouts/footerLinks.blade.php index 45540236..5b62409a 100644 --- a/src/views/layouts/footerLinks.blade.php +++ b/src/views/layouts/footerLinks.blade.php @@ -7,11 +7,13 @@ sounds: {!! json_encode(config('chatify.sounds')) !!}, allowedImages: {!! json_encode(config('chatify.attachments.allowed_images')) !!}, allowedFiles: {!! json_encode(config('chatify.attachments.allowed_files')) !!}, + allowedVoiceMessages: {!! json_encode(config('chatify.attachments.allowed_voice_messages')) !!}, maxUploadSize: {{ Chatify::getMaxUploadSize() }}, pusher: {!! json_encode(config('chatify.pusher')) !!}, pusherAuthEndpoint: '{{route("pusher.auth")}}' }; - window.chatify.allAllowedExtensions = chatify.allowedImages.concat(chatify.allowedFiles); + window.chatify.allAllowedExtensions = chatify.allowedImages.concat(chatify.allowedFiles).concat(chatify.allowedVoiceMessages); + diff --git a/src/views/layouts/headLinks.blade.php b/src/views/layouts/headLinks.blade.php index 630c934f..9a1cc431 100644 --- a/src/views/layouts/headLinks.blade.php +++ b/src/views/layouts/headLinks.blade.php @@ -15,6 +15,7 @@ + {{-- styles --}} diff --git a/src/views/layouts/messageCard.blade.php b/src/views/layouts/messageCard.blade.php index 6c1719de..e01780fe 100644 --- a/src/views/layouts/messageCard.blade.php +++ b/src/views/layouts/messageCard.blade.php @@ -14,7 +14,7 @@ @endif {{-- Card --}}
- @if (@$attachment->type != 'image' || $message) + @if (@$attachment->type == 'file' || $message)
{!! ($message == null && $attachment != null && @$attachment->type != 'file') ? $attachment->title : nl2br($message) !!} {!! $timeAndSeen !!} @@ -27,13 +27,31 @@ @endif @if(@$attachment->type == 'image')
-
+
{{ $attachment->title }}
{!! $timeAndSeen !!}
- @endif + @endif + @if (@$attachment->type == 'audio') +
+ @php($color = Auth::user()->messenger_color ?? 'blue') +
+ + +
+ 0:00 +
+
+ {!! $timeAndSeen !!} +
+ +@endif +
diff --git a/src/views/layouts/sendForm.blade.php b/src/views/layouts/sendForm.blade.php index c435734f..1373328c 100644 --- a/src/views/layouts/sendForm.blade.php +++ b/src/views/layouts/sendForm.blade.php @@ -1,9 +1,26 @@
-
+ @csrf - + + - +
+ + +
From d40401c2e0c076e2360d16212501123841f03567 Mon Sep 17 00:00:00 2001 From: sarahelsagheir <53413980+sarahelsagheir@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:43:01 +0300 Subject: [PATCH 2/5] update wave color & support firefox on ubuntu --- src/assets/css/style.css | 2 ++ src/assets/js/code.js | 28 ++++++++++++++++++------- src/assets/js/voiceMessage.js | 7 +++++-- src/views/layouts/messageCard.blade.php | 4 ++-- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/assets/css/style.css b/src/assets/css/style.css index aab72f33..466566c1 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -1258,6 +1258,7 @@ div.loadingPlaceholder-date { .waveform { width: 80%; overflow: hidden; + margin-left: 12px; position: relative; z-index: 0; cursor: pointer; @@ -1299,6 +1300,7 @@ div.loadingPlaceholder-date { display: flex; align-items: center; width: 100%; + height: 100%; position: relative; z-index: 2; } diff --git a/src/assets/js/code.js b/src/assets/js/code.js index f97206a7..1eb17e6c 100644 --- a/src/assets/js/code.js +++ b/src/assets/js/code.js @@ -79,11 +79,10 @@ function updateSelectedContact(user_id) { ".messenger-list-item[data-contact=" + (user_id) + "]" ) .addClass("m-list-active"); - if (user_id != 0) { IDinfo(user_id); } - } +} /** *------------------------------------------------------------- * Global Templates @@ -466,10 +465,10 @@ function sendMessage(isVoiceMessage = false, audioBlob = null, duration = null, return false; } - formData = new FormData($("#message-form")[0]); - formData.append("id", receiverId); - formData.append("temporaryMsgId", tempID); - formData.append("_token", csrfToken); + formData = new FormData($("#message-form")[0]); + formData.append("id", receiverId); + formData.append("temporaryMsgId", tempID); + formData.append("_token", csrfToken); } @@ -510,7 +509,6 @@ function sendMessage(isVoiceMessage = false, audioBlob = null, duration = null, const tempMsgCardElement = messagesContainer.find( `.message-card[data-id=${data.tempID}]` ); - const message = duration ? data.message.replace(/()\d{1,2}:\d{2}(<\/span>)/, `$1${duration}$2`) : data.message; @@ -539,6 +537,8 @@ function sendMessage(isVoiceMessage = false, audioBlob = null, duration = null, return false; } + + /** *------------------------------------------------------------- * Fetch messages from database @@ -590,11 +590,13 @@ function fetchMessages(id, newFetch = false) { const lastMsg = messagesElement.find( messagesElement.find(".message-card")[0] ); + const curOffset = lastMsg.offset().top - messagesContainer.scrollTop(); messagesElement.prepend(data.messages); messagesContainer.scrollTop(lastMsg.offset().top - curOffset); } + // trigger seen event makeSeen(true); // Pagination lock & messages page @@ -660,6 +662,16 @@ channel.bind("messaging", function (data) { if (data.from_id == getMessengerId() && data.to_id == auth_id) { $(".messages").find(".message-hint").remove(); messagesContainer.find(".messages").append(data.message); + + if (data.message.includes("data-audio-url")) { + const actualMessageIdMatch = data.message.match(/data-id="([a-z0-9\-]+)"/); + const actualMessageId = actualMessageIdMatch ? actualMessageIdMatch[1] : null; + + if (actualMessageId) { + initializeAudioPlayer(actualMessageId); + } + } + scrollToBottom(messagesContainer); makeSeen(true); // remove unseen counter for the user from the contacts list @@ -1359,7 +1371,7 @@ $(document).ready(function () { } const dataId = $(this).find("p[data-id]").attr("data-id"); setMessengerId(dataId); - // IDinfo(dataId); + // IDinfo(dataId); }); // click action for favorite button diff --git a/src/assets/js/voiceMessage.js b/src/assets/js/voiceMessage.js index 0a8555c6..54698c57 100644 --- a/src/assets/js/voiceMessage.js +++ b/src/assets/js/voiceMessage.js @@ -301,12 +301,14 @@ waveColor: "rgb(218, 210, 210)", progressColor: "grey", cursorColor: "transparent", - barWidth: 2, + barWidth: 2.5, barRadius: 3, - height: 36, + height: 28, responsive: true, barGap: 3, hideScrollbar: true, + drawingContextAttributes: { desynchronized: true }, + barMinHeight: 5, }); waveform.classList.add("initialized"); @@ -361,4 +363,5 @@ window.initializeAudioPlayer = initializeAudioPlayer; window.renderWavesurfers = renderWavesurfers; window.rederWavesurfers = renderWavesurfers; + window.renderWaveSurfers = renderWavesurfers; })(); diff --git a/src/views/layouts/messageCard.blade.php b/src/views/layouts/messageCard.blade.php index e01780fe..70bc8c55 100644 --- a/src/views/layouts/messageCard.blade.php +++ b/src/views/layouts/messageCard.blade.php @@ -40,8 +40,8 @@
@php($color = Auth::user()->messenger_color ?? 'blue')
-
From db5855720a6a6bfa80772fc2879d7d20761a9bb9 Mon Sep 17 00:00:00 2001 From: sarahelsagheir Date: Tue, 14 Jul 2026 01:17:18 +0300 Subject: [PATCH 3/5] voice message feature --- src/ChatifyMessenger.php | 33 +++++++++++++++++-- .../Controllers/Api/MessagesController.php | 18 ++-------- src/Http/Controllers/MessagesController.php | 20 ++--------- src/assets/js/voiceMessage.js | 7 +++- src/config/chatify.php | 1 + src/views/layouts/footerLinks.blade.php | 1 + src/views/layouts/listItem.blade.php | 8 +++-- 7 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/ChatifyMessenger.php b/src/ChatifyMessenger.php index 088718df..12596e1b 100644 --- a/src/ChatifyMessenger.php +++ b/src/ChatifyMessenger.php @@ -4,6 +4,7 @@ use App\Models\ChMessage as Message; use App\Models\ChFavorite as Favorite; +use App\Models\User; use Illuminate\Support\Facades\Storage; use Pusher\Pusher; use Illuminate\Support\Facades\Auth; @@ -262,6 +263,33 @@ public function countUnseenMessages($user_id) return Message::where('from_id', $user_id)->where('to_id', Auth::user()->id)->where('seen', 0)->count(); } + /** + * Get contacts ordered by the latest message with each contact. + * + * @param int $authId + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getContactsQuery(int $authId) + { + $contactUserId = 'CASE WHEN from_id = ? THEN to_id ELSE from_id END'; + + $latestMessages = Message::query() + ->selectRaw("$contactUserId as user_id", [$authId]) + ->selectRaw('MAX(created_at) as max_created_at') + ->where(function ($q) use ($authId) { + $q->where('from_id', $authId) + ->orWhere('to_id', $authId); + }) + ->groupBy('user_id'); + + return User::joinSub($latestMessages, 'latest_messages', function ($join) { + $join->on('users.id', '=', 'latest_messages.user_id'); + }) + ->where('users.id', '!=', $authId) + ->select('users.*', 'latest_messages.max_created_at') + ->orderBy('latest_messages.max_created_at', 'desc'); + } + /** * Get user list's item data [Contact Itme] * (e.g. User data, Last message, Unseen Counter...) @@ -278,8 +306,9 @@ public function getContactItem($user) // Get Unseen messages counter $unseenCounter = $this->countUnseenMessages($user->id); if ($lastMessage) { - $lastMessage->created_at = $lastMessage->created_at->toIso8601String(); - $lastMessage->timeAgo = $lastMessage->created_at->diffForHumans(); + $lastMessageCreatedAt = $lastMessage->created_at; + $lastMessage->timeAgo = $lastMessageCreatedAt->diffForHumans(); + $lastMessage->created_at = $lastMessageCreatedAt->toIso8601String(); } return view('Chatify::layouts.listItem', [ 'get' => 'users', diff --git a/src/Http/Controllers/Api/MessagesController.php b/src/Http/Controllers/Api/MessagesController.php index d25299d3..b9ffb88b 100644 --- a/src/Http/Controllers/Api/MessagesController.php +++ b/src/Http/Controllers/Api/MessagesController.php @@ -5,12 +5,10 @@ use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Response; -use App\Models\ChMessage as Message; use App\Models\ChFavorite as Favorite; use Chatify\Facades\ChatifyMessenger as Chatify; use App\Models\User; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -207,20 +205,8 @@ public function seen(Request $request) */ public function getContacts(Request $request) { - // get all users that received/sent message from/to [Auth user] - $users = Message::join('users', function ($join) { - $join->on('ch_messages.from_id', '=', 'users.id') - ->orOn('ch_messages.to_id', '=', 'users.id'); - }) - ->where(function ($q) { - $q->where('ch_messages.from_id', Auth::user()->id) - ->orWhere('ch_messages.to_id', Auth::user()->id); - }) - ->where('users.id','!=',Auth::user()->id) - ->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at')) - ->orderBy('max_created_at', 'desc') - ->groupBy('users.id') - ->paginate($request->per_page ?? $this->perPage); + $authId = (int) Auth::user()->id; + $users = Chatify::getContactsQuery($authId)->paginate($request->per_page ?? $this->perPage); return response()->json([ 'contacts' => $users->items(), diff --git a/src/Http/Controllers/MessagesController.php b/src/Http/Controllers/MessagesController.php index d42aeb05..2508f388 100644 --- a/src/Http/Controllers/MessagesController.php +++ b/src/Http/Controllers/MessagesController.php @@ -7,11 +7,9 @@ use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Response; use App\Models\User; -use App\Models\ChMessage as Message; use App\Models\ChFavorite as Favorite; use Chatify\Facades\ChatifyMessenger as Chatify; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Request as FacadesRequest; use Illuminate\Support\Str; @@ -187,7 +185,7 @@ private function handleTextOrFileMessage(Request $request, &$error, &$attachment */ private function storeUploadedAttachment($file, $allowedExtensions, &$error, &$attachment, &$attachment_title) { - if ($file->getSize() > Chatify::getMaxUploadSize()) { + if ($file->getSize() >= Chatify::getMaxUploadSize()) { $this->setError($error, "File size you are trying to upload is too large!"); return; } @@ -319,20 +317,8 @@ public function seen(Request $request) */ public function getContacts(Request $request) { - // get all users that received/sent message from/to [Auth user] - $users = Message::join('users', function ($join) { - $join->on('ch_messages.from_id', '=', 'users.id') - ->orOn('ch_messages.to_id', '=', 'users.id'); - }) - ->where(function ($q) { - $q->where('ch_messages.from_id', Auth::user()->id) - ->orWhere('ch_messages.to_id', Auth::user()->id); - }) - ->where('users.id','!=',Auth::user()->id) - ->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at')) - ->orderBy('max_created_at', 'desc') - ->groupBy('users.id') - ->paginate($request->per_page ?? $this->perPage); + $authId = (int) Auth::user()->id; + $users = Chatify::getContactsQuery($authId)->paginate($request->per_page ?? $this->perPage); $usersList = $users->items(); diff --git a/src/assets/js/voiceMessage.js b/src/assets/js/voiceMessage.js index 54698c57..d8e91c42 100644 --- a/src/assets/js/voiceMessage.js +++ b/src/assets/js/voiceMessage.js @@ -4,7 +4,12 @@ **************************************************************************** */ (function () { - const MAX_RECORDING_TIME = 60; + const DEFAULT_MAX_RECORDING_TIME = 60; + const configuredMaxRecordingTime = Number.parseInt(window.chatify?.maxVoiceRecordingTime, 10); + const MAX_RECORDING_TIME = + Number.isFinite(configuredMaxRecordingTime) && configuredMaxRecordingTime > 0 + ? configuredMaxRecordingTime + : DEFAULT_MAX_RECORDING_TIME; const AUDIO_TYPES = [ { mimeType: "audio/webm;codecs=opus", extension: "webm" }, { mimeType: "audio/webm", extension: "webm" }, diff --git a/src/config/chatify.php b/src/config/chatify.php index e28c5293..b1103f16 100644 --- a/src/config/chatify.php +++ b/src/config/chatify.php @@ -88,6 +88,7 @@ 'allowed_images' => (array) ['png','jpg','jpeg','gif'], 'allowed_files' => (array) ['zip','rar','txt'], 'allowed_voice_messages' => (array) ['mpeg','mpga','mp3','wav','webm','mp4'], + 'max_voice_recording_time' => env('CHATIFY_MAX_VOICE_RECORDING_TIME', 60), // seconds 'max_upload_size' => env('CHATIFY_MAX_FILE_SIZE', 150), // MB ], diff --git a/src/views/layouts/footerLinks.blade.php b/src/views/layouts/footerLinks.blade.php index 5b62409a..6efe2870 100644 --- a/src/views/layouts/footerLinks.blade.php +++ b/src/views/layouts/footerLinks.blade.php @@ -8,6 +8,7 @@ allowedImages: {!! json_encode(config('chatify.attachments.allowed_images')) !!}, allowedFiles: {!! json_encode(config('chatify.attachments.allowed_files')) !!}, allowedVoiceMessages: {!! json_encode(config('chatify.attachments.allowed_voice_messages')) !!}, + maxVoiceRecordingTime: {{ (int) config('chatify.attachments.max_voice_recording_time', 60) }}, maxUploadSize: {{ Chatify::getMaxUploadSize() }}, pusher: {!! json_encode(config('chatify.pusher')) !!}, pusherAuthEndpoint: '{{route("pusher.auth")}}' diff --git a/src/views/layouts/listItem.blade.php b/src/views/layouts/listItem.blade.php index a1436ab5..e1fddd46 100644 --- a/src/views/layouts/listItem.blade.php +++ b/src/views/layouts/listItem.blade.php @@ -20,8 +20,11 @@ {{-- -------------------- Contact list -------------------- --}} @if($get == 'users' && !!$lastMessage) body, 'UTF-8', 'UTF-8'); +$lastMessageBody = mb_convert_encoding((string) ($lastMessage->body ?? ''), 'UTF-8', 'UTF-8'); $lastMessageBody = strlen($lastMessageBody) > 30 ? mb_substr($lastMessageBody, 0, 30, 'UTF-8').'..' : $lastMessageBody; +$lastMessageAttachment = $lastMessage->attachment ? json_decode($lastMessage->attachment) : null; +$lastMessageExtension = $lastMessageAttachment ? strtolower(pathinfo($lastMessageAttachment->new_name ?? '', PATHINFO_EXTENSION)) : null; +$lastMessageIsVoice = $lastMessageExtension && in_array($lastMessageExtension, config('chatify.attachments.allowed_voice_messages')); ?> @@ -51,6 +54,8 @@ {!! $lastMessageBody !!} + @elseif($lastMessageIsVoice) + Voice message @else Attachment @endif @@ -87,4 +92,3 @@
@endif - From 54b87c775348a68584e6e4a2a9dbb06350fd2ebe Mon Sep 17 00:00:00 2001 From: sarahelsagheir Date: Tue, 14 Jul 2026 11:52:30 +0300 Subject: [PATCH 4/5] Use content-based extension guessing instead of client-supplied extension --- src/Http/Controllers/MessagesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/MessagesController.php b/src/Http/Controllers/MessagesController.php index 2508f388..a89cf87a 100644 --- a/src/Http/Controllers/MessagesController.php +++ b/src/Http/Controllers/MessagesController.php @@ -190,7 +190,7 @@ private function storeUploadedAttachment($file, $allowedExtensions, &$error, &$a return; } - $extension = strtolower($file->getClientOriginalExtension()); + $extension = strtolower($file->extension()); if (!in_array($extension, $allowedExtensions)) { $this->setError($error, "File extension not allowed!"); From b8ec0dd984d7d6dc23f195da7b795eae9bc0adfd Mon Sep 17 00:00:00 2001 From: sarahelsagheir Date: Tue, 14 Jul 2026 11:55:30 +0300 Subject: [PATCH 5/5] remove unneeded renderWaveSurfers --- src/assets/js/voiceMessage.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/assets/js/voiceMessage.js b/src/assets/js/voiceMessage.js index d8e91c42..2851aabb 100644 --- a/src/assets/js/voiceMessage.js +++ b/src/assets/js/voiceMessage.js @@ -367,6 +367,4 @@ window.initializeAudioPlayer = initializeAudioPlayer; window.renderWavesurfers = renderWavesurfers; - window.rederWavesurfers = renderWavesurfers; - window.renderWaveSurfers = renderWavesurfers; })();