├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── art └── preview.png ├── composer.json ├── composer.lock └── src ├── ChatifyMessenger.php ├── ChatifyServiceProvider.php ├── Console ├── InstallCommand.php └── PublishCommand.php ├── Facades └── ChatifyMessenger.php ├── Http └── Controllers │ ├── Api │ └── MessagesController.php │ └── MessagesController.php ├── MessageCollection.php ├── Models ├── ChFavorite.php └── ChMessage.php ├── Traits ├── HasMessage.php └── UUID.php ├── assets ├── css │ ├── dark.mode.css │ ├── light.mode.css │ └── style.css ├── imgs │ └── avatar.png ├── js │ ├── autosize.js │ ├── code.js │ ├── font.awesome.min.js │ └── utils.js └── sounds │ └── new-message-sound.mp3 ├── config └── chatify.php ├── database └── migrations │ ├── 2022_01_10_99999_add_active_status_to_users.php │ ├── 2022_01_10_99999_add_avatar_to_users.php │ ├── 2022_01_10_99999_add_dark_mode_to_users.php │ ├── 2022_01_10_99999_add_messenger_color_to_users.php │ ├── 2022_01_10_99999_create_chatify_favorites_table.php │ └── 2022_01_10_99999_create_chatify_messages_table.php ├── routes ├── api.php └── web.php └── views ├── layouts ├── favorite.blade.php ├── footerLinks.blade.php ├── headLinks.blade.php ├── info.blade.php ├── listItem.blade.php ├── messageCard.blade.php ├── modals.blade.php └── sendForm.blade.php └── pages └── app.blade.php /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## v1.6.5 () 6 | 7 | ### Fixed 8 | - Settings modal UI #351 9 | - Limit the data retrieved for the user #359 10 | - [FIX] - Init user conversation from URL #374 11 | - Sanitize inputs to prevent xss when sending message #377 12 | 13 | ## v1.6.4 (2024-04-27) 14 | 15 | ### Fixed 16 | - [fix bug] updateSelectedContact and IDinfo load if user_id != auth_id #339 17 | 18 | ## v1.6.3 (2024-03-17) 19 | 20 | ### Added 21 | 22 | - Support for a custom routes. 23 | 24 | ## v1.6.2 (2023-07-27) 25 | 26 | ### Added 27 | 28 | - Support for a custom WS server #291. 29 | 30 | ## v1.6.1 (2023-03-03) 31 | 32 | ### Fixed 33 | 34 | - Migration files issue (Cannot redeclare class...). 35 | 36 | ## v1.6.0 (2023-03-03) 37 | 38 | ### Added 39 | 40 | - Emoji's support. 41 | - Css variables. 42 | - Notification sounds. 43 | - Auto-time updates. 44 | 45 | ### Changed 46 | 47 | - Using UUIDs instead of random IDs on table primary column #243. 48 | - UI/UX changes and enhancements. 49 | - Code refactored (part of it). 50 | - Messenger primary color fallback. 51 | 52 | ### Fixed 53 | 54 | - Fetching messages multiple times at once on send/fetch requests. 55 | - Migrations duplicate class name. 56 | - Prevent chat for invalid user ids #246 57 | - Fix responsiveness when going to chat with specific ID #247. 58 | - App URL should be changed when click the `back to contacts` button on small screens. 59 | - Internet connection UI. 60 | - Prevent Users from updating each others statuses #254 61 | - Contact list realtime updates issues. 62 | - Delete messages issues. 63 | - Fix contact list error `Malformed UTF-8 characters, possibly incorrectly encoded` 64 | - Search multiple request on typing, debouncing used. 65 | 66 | ## v1.5.6 (2023-01-26) 67 | 68 | ### Fixed 69 | 70 | - Keyboard overlaping on input issue on mobile #202. 71 | - Security issue and code enhancements #240. 72 | 73 | ## v1.5.5 (2023-01-21) 74 | 75 | ### Fixed 76 | 77 | - message delete event channel #238. 78 | 79 | ## v1.5.4 (2022-12-05) 80 | 81 | ### Fixed 82 | 83 | - Channels auth secutiy issue #29 84 | 85 | ## v1.5.3 (2022-12-04) 86 | 87 | ### Fixed 88 | 89 | - Channels Secutiy issue #29 90 | 91 | ## v1.5.2 (2022-07-08) 92 | 93 | ### Fixed 94 | 95 | - MessageCard & fetchMessage methods@`ChatifyMessenger.php` fallback. 96 | 97 | ## v1.5.1 (2022-06-09) 98 | 99 | ### Fixed 100 | 101 | - Sync the `sending a message form`'s allowed files/images with the `config` file (Update sendForm.blade.php [#190](https://github.com/munafio/chatify/pull/190)) 102 | 103 | ## v1.5.0 (2022-06-08) 104 | 105 | ### Added 106 | 107 | - Page/Document visibility Support which improves (seen) feature #183 108 | 109 | ### Fixed 110 | 111 | - fix: case insensitive file upload extension check #182 112 | 113 | ## v1.4.0 (2022-05-02) 114 | 115 | ### Added 116 | 117 | - [Gravatar](https:://gravatar.com) support (optional, can be changed at config/chatify.php). 118 | - Delete Message by ID. 119 | - Laravel's Storage disk now supported and can be changed from the config. 120 | 121 | ### Changed 122 | 123 | - File upload (user avatar & attachments) `allowed files` and `max size` now can be changed from one place which is (config/chatify.php). 124 | 125 | ### Fixed 126 | 127 | - Bugs and UI/UX design fixes/improvements. 128 | 129 | ## v1.3.4 (2022-02-04) 130 | 131 | ### Fixed 132 | 133 | - Fixed Installing errors on the migrations step. #163 134 | 135 | ## v1.3.3 (2022-01-10) 136 | 137 | ### Fixed 138 | 139 | - Fixed file upload size limit error message rephrase #160. 140 | 141 | ### Changed 142 | 143 | - Files max upload size changed & added to the config to be customizable. 144 | - Changed `Messenger colors` logic to be more flexible and customizable. 145 | - Migration files renamed, file date automatically will be changed to the publish/install date. 146 | 147 | ## v1.3.2 (2022-01-07) 148 | 149 | ### Fixed 150 | 151 | - Fixed CSS issue in FF with the contact list #157. 152 | - Correct misspelt of `updateContactItem` method (typo error) #159. 153 | 154 | ## v1.3.1 (2021-12-23) 155 | 156 | ### Fixed 157 | 158 | - Fixed migration's rollback, (ch\_) prefix added. 159 | 160 | ## v1.3.0 (2021-11-30) 161 | 162 | ### Fixed 163 | 164 | - UI/Ux fixes & improvements. 165 | - Backend fixes & improvements. 166 | 167 | ### Added 168 | 169 | - Messages, Contacts, and Search pagination. 170 | - API routes. 171 | 172 | ## v1.2.5 (2021-08-18) 173 | 174 | ### Fixed 175 | 176 | - Fixed a security issue on uploaded file-name, which is vulnerable with XSS. 177 | 178 | ## v1.2.4 (2021-07-15) 179 | 180 | ### Fixed 181 | 182 | - README updates. 183 | - Install Command fixes & improvements. 184 | - Contact list visible onLoad. 185 | - Settings’ modal responsive design. 186 | 187 | ### Added 188 | 189 | - UPGRADE.md added. 190 | - Publish command added. 191 | - Package.json additions & modifications. 192 | 193 | ## v1.2.3 - (2021-06-19) 194 | 195 | ### Fixed 196 | 197 | - XSS issue on inputs. 198 | - UI/UX fixes & improvements. 199 | - Send message fixes (UI & backend). 200 | - Update Profile Settings (upload file & error handling ….). 201 | - Shared photos not working issue. 202 | - Typo error fixes (Your `contatc` list is empty). 203 | - Rolling back migrations added. 204 | - Get Last message `orderBy` query duplication. 205 | 206 | ## v1.2.2 - (2021-06-01) 207 | 208 | ### Fixed 209 | 210 | - Migrate to database command removed. 211 | - Publishable asset `assets` avatar config issue. 212 | - Pusher encryption key option removed. 213 | - Settings button on click not working issue. 214 | 215 | ## v1.2.1 - (2021-05-30) 216 | 217 | ### Fixed 218 | 219 | - Publishable asset `assets`. 220 | 221 | ## v1.2.0 - (2021-05-30) 222 | 223 | ### FIxed 224 | 225 | - Security issues. 226 | - UI/UX issues. 227 | - Route [home] not defiend. 228 | - `$msg->attachment` issue #9. 229 | - Delete conversation issue #89. 230 | 231 | ### Added 232 | 233 | - Console commands. 234 | - `Models` added to assets to be published. 235 | - Laravel 8+ support. 236 | 237 | ### Changed 238 | 239 | - Project structure. 240 | - composer updated `pusher/pusher-php-server` to v^7.0. 241 | - Models & Migrations' tables names changed (added `ch` prefix to avoid duplication) solves issue #68. 242 | - Models changed to (`ChMessage`, `ChFavorite`) 243 | - Migrations' tables names (`ch_messages`, `ch_favorites`) 244 | - Configuration file `config/chatify.php`. 245 | 246 | ## v1.0.1 - (2020-09-30) 247 | 248 | ### FIxed 249 | 250 | - Security issues. 251 | 252 | ### Added 253 | 254 | - Routes' controllers namespace included in the configuration. 255 | 256 | ## v1.0.0 - (2019-12-30) 257 | 258 | - First release 259 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Munaf A. Mahdi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Chatify Laravel Package

2 | 3 |

4 | Build Status 5 | Total Downloads 6 | License 7 |

8 | 9 | ## Chatify Laravel Package 10 | 11 | Laravel's #1 one-to-one chatting system package, helps you add a complete real-time chatting system to your new/existing Laravel application with only one command. 12 | 13 | ## Need a Help? 📣 14 | 15 | I have created a server for **Chatify** on `Discord` to let you **up-to-date** and help you as much as I can .. so now you can chat with me, get a help, showcases, and most importantly to get announcements and updates about **Chatify**. 16 | 17 | So, [join now](https://discord.gg/RaxyKVykYJ) and keep updated. 18 | 19 | ## Features 20 | 21 | - One-to-one users chat system. 22 | - Real-time contact list updates. 23 | - Favorite users system (Like stories style). 24 | - Saved Messages to save your messages online like Telegram messenger app. 25 | - Search functionality. 26 | - Contact item's last message indicator (e.g. You: ....). 27 | - Real-time user's active status. 28 | - Real-time typing indicator. 29 | - Real-time message seen indicator. 30 | - Real-time internet connection status. 31 | - Upload attachments (Photo/File). 32 | - Send Emoji's. 33 | - User details panel (Shared photos, delete conversation..). 34 | - Responsive design with all devices. 35 | - User settings and chat customization : user's profile photo, dark mode and chat color. 36 | with simple and wonderful UI design. 37 | 38 | ...and much more you have to discover it yourself. 39 | 40 | ## Demo 41 | 42 | - Demo app - [Click Here](https://github.com/munafio/chatify-demo). 43 | 44 | 45 | ## Official Documentation 46 | 47 | The official documentation can be found [here](https://chatify.munafio.com) 48 | 49 | ## Change log 50 | 51 | [CHANGELOG.md](https://github.com/munafio/chatify/blob/master/CHANGELOG.md) 52 | 53 | ## Author 54 | 55 | - [Munaf A. Mahdi](https://www.munafio.com) 56 | 57 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | With every upgrade, make sure to re-publish Chatify's assets: 4 | 5 | ## For v1.2.3 and earlier versions 6 | 7 | ``` 8 | php artisan vendor:publish --tag=chatify-views --force 9 | php artisan vendor:publish --tag=chatify-assets --force 10 | ``` 11 | 12 | If needed, you can re-publish the other assets the same way above by just replacing the name of the asset (chatify-NAME). 13 | 14 | ## For v1.2.4+ and higher vertions 15 | 16 | To re-publish only `views` & `assets`: 17 | 18 | ``` 19 | php artisan chatify:publish 20 | ``` 21 | 22 | To re-publish all the assets (views, assets, config..): 23 | 24 | ``` 25 | php artisan chatify:publish --force 26 | ``` 27 | 28 | > This will overwrite all the assets, so all your changes will be overwritten. 29 | -------------------------------------------------------------------------------- /art/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/munafio/chatify/11cefa98ee9563c166f68605c97a03b5e46679b1/art/preview.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "munafio/chatify", 3 | "description": "A package for Laravel PHP Framework to add a complete real-time chat system.", 4 | "type": "library", 5 | "keywords": [ 6 | "laravel", 7 | "messenger", 8 | "conversations", 9 | "chat", 10 | "php", 11 | "pusher", 12 | "realtime", 13 | "real-time", 14 | "chatify" 15 | ], 16 | "license": "MIT", 17 | "authors": [ 18 | { 19 | "name": "Munaf A. Mahdi", 20 | "email": "munafaqeelmahdi@gmail.com" 21 | } 22 | ], 23 | "homepage": "https://github.com/munafio/chatify", 24 | "minimum-stability": "dev", 25 | "prefer-stable": true, 26 | "require": { 27 | "pusher/pusher-php-server": "^6.0|^7.0|^7.1" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Chatify\\": "src/" 32 | } 33 | }, 34 | "extra": { 35 | "laravel": { 36 | "providers": [ 37 | "Chatify\\ChatifyServiceProvider" 38 | ], 39 | "aliases": { 40 | "Chatify": "Chatify\\Facades\\ChatifyMessenger" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "c6ad250178ab650dd81c52f63abcfafd", 8 | "packages": [ 9 | { 10 | "name": "guzzlehttp/guzzle", 11 | "version": "7.5.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/guzzle/guzzle.git", 15 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", 20 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-json": "*", 25 | "guzzlehttp/promises": "^1.5", 26 | "guzzlehttp/psr7": "^1.9 || ^2.4", 27 | "php": "^7.2.5 || ^8.0", 28 | "psr/http-client": "^1.0", 29 | "symfony/deprecation-contracts": "^2.2 || ^3.0" 30 | }, 31 | "provide": { 32 | "psr/http-client-implementation": "1.0" 33 | }, 34 | "require-dev": { 35 | "bamarni/composer-bin-plugin": "^1.8.1", 36 | "ext-curl": "*", 37 | "php-http/client-integration-tests": "^3.0", 38 | "phpunit/phpunit": "^8.5.29 || ^9.5.23", 39 | "psr/log": "^1.1 || ^2.0 || ^3.0" 40 | }, 41 | "suggest": { 42 | "ext-curl": "Required for CURL handler support", 43 | "ext-intl": "Required for Internationalized Domain Name (IDN) support", 44 | "psr/log": "Required for using the Log middleware" 45 | }, 46 | "type": "library", 47 | "extra": { 48 | "bamarni-bin": { 49 | "bin-links": true, 50 | "forward-command": false 51 | }, 52 | "branch-alias": { 53 | "dev-master": "7.5-dev" 54 | } 55 | }, 56 | "autoload": { 57 | "files": [ 58 | "src/functions_include.php" 59 | ], 60 | "psr-4": { 61 | "GuzzleHttp\\": "src/" 62 | } 63 | }, 64 | "notification-url": "https://packagist.org/downloads/", 65 | "license": [ 66 | "MIT" 67 | ], 68 | "authors": [ 69 | { 70 | "name": "Graham Campbell", 71 | "email": "hello@gjcampbell.co.uk", 72 | "homepage": "https://github.com/GrahamCampbell" 73 | }, 74 | { 75 | "name": "Michael Dowling", 76 | "email": "mtdowling@gmail.com", 77 | "homepage": "https://github.com/mtdowling" 78 | }, 79 | { 80 | "name": "Jeremy Lindblom", 81 | "email": "jeremeamia@gmail.com", 82 | "homepage": "https://github.com/jeremeamia" 83 | }, 84 | { 85 | "name": "George Mponos", 86 | "email": "gmponos@gmail.com", 87 | "homepage": "https://github.com/gmponos" 88 | }, 89 | { 90 | "name": "Tobias Nyholm", 91 | "email": "tobias.nyholm@gmail.com", 92 | "homepage": "https://github.com/Nyholm" 93 | }, 94 | { 95 | "name": "Márk Sági-Kazár", 96 | "email": "mark.sagikazar@gmail.com", 97 | "homepage": "https://github.com/sagikazarmark" 98 | }, 99 | { 100 | "name": "Tobias Schultze", 101 | "email": "webmaster@tubo-world.de", 102 | "homepage": "https://github.com/Tobion" 103 | } 104 | ], 105 | "description": "Guzzle is a PHP HTTP client library", 106 | "keywords": [ 107 | "client", 108 | "curl", 109 | "framework", 110 | "http", 111 | "http client", 112 | "psr-18", 113 | "psr-7", 114 | "rest", 115 | "web service" 116 | ], 117 | "support": { 118 | "issues": "https://github.com/guzzle/guzzle/issues", 119 | "source": "https://github.com/guzzle/guzzle/tree/7.5.0" 120 | }, 121 | "funding": [ 122 | { 123 | "url": "https://github.com/GrahamCampbell", 124 | "type": "github" 125 | }, 126 | { 127 | "url": "https://github.com/Nyholm", 128 | "type": "github" 129 | }, 130 | { 131 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", 132 | "type": "tidelift" 133 | } 134 | ], 135 | "time": "2022-08-28T15:39:27+00:00" 136 | }, 137 | { 138 | "name": "guzzlehttp/promises", 139 | "version": "1.5.2", 140 | "source": { 141 | "type": "git", 142 | "url": "https://github.com/guzzle/promises.git", 143 | "reference": "b94b2807d85443f9719887892882d0329d1e2598" 144 | }, 145 | "dist": { 146 | "type": "zip", 147 | "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", 148 | "reference": "b94b2807d85443f9719887892882d0329d1e2598", 149 | "shasum": "" 150 | }, 151 | "require": { 152 | "php": ">=5.5" 153 | }, 154 | "require-dev": { 155 | "symfony/phpunit-bridge": "^4.4 || ^5.1" 156 | }, 157 | "type": "library", 158 | "extra": { 159 | "branch-alias": { 160 | "dev-master": "1.5-dev" 161 | } 162 | }, 163 | "autoload": { 164 | "files": [ 165 | "src/functions_include.php" 166 | ], 167 | "psr-4": { 168 | "GuzzleHttp\\Promise\\": "src/" 169 | } 170 | }, 171 | "notification-url": "https://packagist.org/downloads/", 172 | "license": [ 173 | "MIT" 174 | ], 175 | "authors": [ 176 | { 177 | "name": "Graham Campbell", 178 | "email": "hello@gjcampbell.co.uk", 179 | "homepage": "https://github.com/GrahamCampbell" 180 | }, 181 | { 182 | "name": "Michael Dowling", 183 | "email": "mtdowling@gmail.com", 184 | "homepage": "https://github.com/mtdowling" 185 | }, 186 | { 187 | "name": "Tobias Nyholm", 188 | "email": "tobias.nyholm@gmail.com", 189 | "homepage": "https://github.com/Nyholm" 190 | }, 191 | { 192 | "name": "Tobias Schultze", 193 | "email": "webmaster@tubo-world.de", 194 | "homepage": "https://github.com/Tobion" 195 | } 196 | ], 197 | "description": "Guzzle promises library", 198 | "keywords": [ 199 | "promise" 200 | ], 201 | "support": { 202 | "issues": "https://github.com/guzzle/promises/issues", 203 | "source": "https://github.com/guzzle/promises/tree/1.5.2" 204 | }, 205 | "funding": [ 206 | { 207 | "url": "https://github.com/GrahamCampbell", 208 | "type": "github" 209 | }, 210 | { 211 | "url": "https://github.com/Nyholm", 212 | "type": "github" 213 | }, 214 | { 215 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", 216 | "type": "tidelift" 217 | } 218 | ], 219 | "time": "2022-08-28T14:55:35+00:00" 220 | }, 221 | { 222 | "name": "guzzlehttp/psr7", 223 | "version": "2.4.3", 224 | "source": { 225 | "type": "git", 226 | "url": "https://github.com/guzzle/psr7.git", 227 | "reference": "67c26b443f348a51926030c83481b85718457d3d" 228 | }, 229 | "dist": { 230 | "type": "zip", 231 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", 232 | "reference": "67c26b443f348a51926030c83481b85718457d3d", 233 | "shasum": "" 234 | }, 235 | "require": { 236 | "php": "^7.2.5 || ^8.0", 237 | "psr/http-factory": "^1.0", 238 | "psr/http-message": "^1.0", 239 | "ralouphie/getallheaders": "^3.0" 240 | }, 241 | "provide": { 242 | "psr/http-factory-implementation": "1.0", 243 | "psr/http-message-implementation": "1.0" 244 | }, 245 | "require-dev": { 246 | "bamarni/composer-bin-plugin": "^1.8.1", 247 | "http-interop/http-factory-tests": "^0.9", 248 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 249 | }, 250 | "suggest": { 251 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 252 | }, 253 | "type": "library", 254 | "extra": { 255 | "bamarni-bin": { 256 | "bin-links": true, 257 | "forward-command": false 258 | }, 259 | "branch-alias": { 260 | "dev-master": "2.4-dev" 261 | } 262 | }, 263 | "autoload": { 264 | "psr-4": { 265 | "GuzzleHttp\\Psr7\\": "src/" 266 | } 267 | }, 268 | "notification-url": "https://packagist.org/downloads/", 269 | "license": [ 270 | "MIT" 271 | ], 272 | "authors": [ 273 | { 274 | "name": "Graham Campbell", 275 | "email": "hello@gjcampbell.co.uk", 276 | "homepage": "https://github.com/GrahamCampbell" 277 | }, 278 | { 279 | "name": "Michael Dowling", 280 | "email": "mtdowling@gmail.com", 281 | "homepage": "https://github.com/mtdowling" 282 | }, 283 | { 284 | "name": "George Mponos", 285 | "email": "gmponos@gmail.com", 286 | "homepage": "https://github.com/gmponos" 287 | }, 288 | { 289 | "name": "Tobias Nyholm", 290 | "email": "tobias.nyholm@gmail.com", 291 | "homepage": "https://github.com/Nyholm" 292 | }, 293 | { 294 | "name": "Márk Sági-Kazár", 295 | "email": "mark.sagikazar@gmail.com", 296 | "homepage": "https://github.com/sagikazarmark" 297 | }, 298 | { 299 | "name": "Tobias Schultze", 300 | "email": "webmaster@tubo-world.de", 301 | "homepage": "https://github.com/Tobion" 302 | }, 303 | { 304 | "name": "Márk Sági-Kazár", 305 | "email": "mark.sagikazar@gmail.com", 306 | "homepage": "https://sagikazarmark.hu" 307 | } 308 | ], 309 | "description": "PSR-7 message implementation that also provides common utility methods", 310 | "keywords": [ 311 | "http", 312 | "message", 313 | "psr-7", 314 | "request", 315 | "response", 316 | "stream", 317 | "uri", 318 | "url" 319 | ], 320 | "support": { 321 | "issues": "https://github.com/guzzle/psr7/issues", 322 | "source": "https://github.com/guzzle/psr7/tree/2.4.3" 323 | }, 324 | "funding": [ 325 | { 326 | "url": "https://github.com/GrahamCampbell", 327 | "type": "github" 328 | }, 329 | { 330 | "url": "https://github.com/Nyholm", 331 | "type": "github" 332 | }, 333 | { 334 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 335 | "type": "tidelift" 336 | } 337 | ], 338 | "time": "2022-10-26T14:07:24+00:00" 339 | }, 340 | { 341 | "name": "paragonie/random_compat", 342 | "version": "v9.99.100", 343 | "source": { 344 | "type": "git", 345 | "url": "https://github.com/paragonie/random_compat.git", 346 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" 347 | }, 348 | "dist": { 349 | "type": "zip", 350 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", 351 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", 352 | "shasum": "" 353 | }, 354 | "require": { 355 | "php": ">= 7" 356 | }, 357 | "require-dev": { 358 | "phpunit/phpunit": "4.*|5.*", 359 | "vimeo/psalm": "^1" 360 | }, 361 | "suggest": { 362 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 363 | }, 364 | "type": "library", 365 | "notification-url": "https://packagist.org/downloads/", 366 | "license": [ 367 | "MIT" 368 | ], 369 | "authors": [ 370 | { 371 | "name": "Paragon Initiative Enterprises", 372 | "email": "security@paragonie.com", 373 | "homepage": "https://paragonie.com" 374 | } 375 | ], 376 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 377 | "keywords": [ 378 | "csprng", 379 | "polyfill", 380 | "pseudorandom", 381 | "random" 382 | ], 383 | "support": { 384 | "email": "info@paragonie.com", 385 | "issues": "https://github.com/paragonie/random_compat/issues", 386 | "source": "https://github.com/paragonie/random_compat" 387 | }, 388 | "time": "2020-10-15T08:29:30+00:00" 389 | }, 390 | { 391 | "name": "paragonie/sodium_compat", 392 | "version": "v1.19.0", 393 | "source": { 394 | "type": "git", 395 | "url": "https://github.com/paragonie/sodium_compat.git", 396 | "reference": "cb15e403ecbe6a6cc515f855c310eb6b1872a933" 397 | }, 398 | "dist": { 399 | "type": "zip", 400 | "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/cb15e403ecbe6a6cc515f855c310eb6b1872a933", 401 | "reference": "cb15e403ecbe6a6cc515f855c310eb6b1872a933", 402 | "shasum": "" 403 | }, 404 | "require": { 405 | "paragonie/random_compat": ">=1", 406 | "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" 407 | }, 408 | "require-dev": { 409 | "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" 410 | }, 411 | "suggest": { 412 | "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", 413 | "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." 414 | }, 415 | "type": "library", 416 | "autoload": { 417 | "files": [ 418 | "autoload.php" 419 | ] 420 | }, 421 | "notification-url": "https://packagist.org/downloads/", 422 | "license": [ 423 | "ISC" 424 | ], 425 | "authors": [ 426 | { 427 | "name": "Paragon Initiative Enterprises", 428 | "email": "security@paragonie.com" 429 | }, 430 | { 431 | "name": "Frank Denis", 432 | "email": "jedisct1@pureftpd.org" 433 | } 434 | ], 435 | "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", 436 | "keywords": [ 437 | "Authentication", 438 | "BLAKE2b", 439 | "ChaCha20", 440 | "ChaCha20-Poly1305", 441 | "Chapoly", 442 | "Curve25519", 443 | "Ed25519", 444 | "EdDSA", 445 | "Edwards-curve Digital Signature Algorithm", 446 | "Elliptic Curve Diffie-Hellman", 447 | "Poly1305", 448 | "Pure-PHP cryptography", 449 | "RFC 7748", 450 | "RFC 8032", 451 | "Salpoly", 452 | "Salsa20", 453 | "X25519", 454 | "XChaCha20-Poly1305", 455 | "XSalsa20-Poly1305", 456 | "Xchacha20", 457 | "Xsalsa20", 458 | "aead", 459 | "cryptography", 460 | "ecdh", 461 | "elliptic curve", 462 | "elliptic curve cryptography", 463 | "encryption", 464 | "libsodium", 465 | "php", 466 | "public-key cryptography", 467 | "secret-key cryptography", 468 | "side-channel resistant" 469 | ], 470 | "support": { 471 | "issues": "https://github.com/paragonie/sodium_compat/issues", 472 | "source": "https://github.com/paragonie/sodium_compat/tree/v1.19.0" 473 | }, 474 | "time": "2022-09-26T03:40:35+00:00" 475 | }, 476 | { 477 | "name": "psr/http-client", 478 | "version": "1.0.1", 479 | "source": { 480 | "type": "git", 481 | "url": "https://github.com/php-fig/http-client.git", 482 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" 483 | }, 484 | "dist": { 485 | "type": "zip", 486 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 487 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 488 | "shasum": "" 489 | }, 490 | "require": { 491 | "php": "^7.0 || ^8.0", 492 | "psr/http-message": "^1.0" 493 | }, 494 | "type": "library", 495 | "extra": { 496 | "branch-alias": { 497 | "dev-master": "1.0.x-dev" 498 | } 499 | }, 500 | "autoload": { 501 | "psr-4": { 502 | "Psr\\Http\\Client\\": "src/" 503 | } 504 | }, 505 | "notification-url": "https://packagist.org/downloads/", 506 | "license": [ 507 | "MIT" 508 | ], 509 | "authors": [ 510 | { 511 | "name": "PHP-FIG", 512 | "homepage": "http://www.php-fig.org/" 513 | } 514 | ], 515 | "description": "Common interface for HTTP clients", 516 | "homepage": "https://github.com/php-fig/http-client", 517 | "keywords": [ 518 | "http", 519 | "http-client", 520 | "psr", 521 | "psr-18" 522 | ], 523 | "support": { 524 | "source": "https://github.com/php-fig/http-client/tree/master" 525 | }, 526 | "time": "2020-06-29T06:28:15+00:00" 527 | }, 528 | { 529 | "name": "psr/http-factory", 530 | "version": "1.0.1", 531 | "source": { 532 | "type": "git", 533 | "url": "https://github.com/php-fig/http-factory.git", 534 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" 535 | }, 536 | "dist": { 537 | "type": "zip", 538 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", 539 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", 540 | "shasum": "" 541 | }, 542 | "require": { 543 | "php": ">=7.0.0", 544 | "psr/http-message": "^1.0" 545 | }, 546 | "type": "library", 547 | "extra": { 548 | "branch-alias": { 549 | "dev-master": "1.0.x-dev" 550 | } 551 | }, 552 | "autoload": { 553 | "psr-4": { 554 | "Psr\\Http\\Message\\": "src/" 555 | } 556 | }, 557 | "notification-url": "https://packagist.org/downloads/", 558 | "license": [ 559 | "MIT" 560 | ], 561 | "authors": [ 562 | { 563 | "name": "PHP-FIG", 564 | "homepage": "http://www.php-fig.org/" 565 | } 566 | ], 567 | "description": "Common interfaces for PSR-7 HTTP message factories", 568 | "keywords": [ 569 | "factory", 570 | "http", 571 | "message", 572 | "psr", 573 | "psr-17", 574 | "psr-7", 575 | "request", 576 | "response" 577 | ], 578 | "support": { 579 | "source": "https://github.com/php-fig/http-factory/tree/master" 580 | }, 581 | "time": "2019-04-30T12:38:16+00:00" 582 | }, 583 | { 584 | "name": "psr/http-message", 585 | "version": "1.0.1", 586 | "source": { 587 | "type": "git", 588 | "url": "https://github.com/php-fig/http-message.git", 589 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 590 | }, 591 | "dist": { 592 | "type": "zip", 593 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 594 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 595 | "shasum": "" 596 | }, 597 | "require": { 598 | "php": ">=5.3.0" 599 | }, 600 | "type": "library", 601 | "extra": { 602 | "branch-alias": { 603 | "dev-master": "1.0.x-dev" 604 | } 605 | }, 606 | "autoload": { 607 | "psr-4": { 608 | "Psr\\Http\\Message\\": "src/" 609 | } 610 | }, 611 | "notification-url": "https://packagist.org/downloads/", 612 | "license": [ 613 | "MIT" 614 | ], 615 | "authors": [ 616 | { 617 | "name": "PHP-FIG", 618 | "homepage": "http://www.php-fig.org/" 619 | } 620 | ], 621 | "description": "Common interface for HTTP messages", 622 | "homepage": "https://github.com/php-fig/http-message", 623 | "keywords": [ 624 | "http", 625 | "http-message", 626 | "psr", 627 | "psr-7", 628 | "request", 629 | "response" 630 | ], 631 | "support": { 632 | "source": "https://github.com/php-fig/http-message/tree/master" 633 | }, 634 | "time": "2016-08-06T14:39:51+00:00" 635 | }, 636 | { 637 | "name": "psr/log", 638 | "version": "3.0.0", 639 | "source": { 640 | "type": "git", 641 | "url": "https://github.com/php-fig/log.git", 642 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" 643 | }, 644 | "dist": { 645 | "type": "zip", 646 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", 647 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", 648 | "shasum": "" 649 | }, 650 | "require": { 651 | "php": ">=8.0.0" 652 | }, 653 | "type": "library", 654 | "extra": { 655 | "branch-alias": { 656 | "dev-master": "3.x-dev" 657 | } 658 | }, 659 | "autoload": { 660 | "psr-4": { 661 | "Psr\\Log\\": "src" 662 | } 663 | }, 664 | "notification-url": "https://packagist.org/downloads/", 665 | "license": [ 666 | "MIT" 667 | ], 668 | "authors": [ 669 | { 670 | "name": "PHP-FIG", 671 | "homepage": "https://www.php-fig.org/" 672 | } 673 | ], 674 | "description": "Common interface for logging libraries", 675 | "homepage": "https://github.com/php-fig/log", 676 | "keywords": [ 677 | "log", 678 | "psr", 679 | "psr-3" 680 | ], 681 | "support": { 682 | "source": "https://github.com/php-fig/log/tree/3.0.0" 683 | }, 684 | "time": "2021-07-14T16:46:02+00:00" 685 | }, 686 | { 687 | "name": "pusher/pusher-php-server", 688 | "version": "7.2.2", 689 | "source": { 690 | "type": "git", 691 | "url": "https://github.com/pusher/pusher-http-php.git", 692 | "reference": "4ace4873873b06c25cecb2dd6d9fdcbf2f20b640" 693 | }, 694 | "dist": { 695 | "type": "zip", 696 | "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/4ace4873873b06c25cecb2dd6d9fdcbf2f20b640", 697 | "reference": "4ace4873873b06c25cecb2dd6d9fdcbf2f20b640", 698 | "shasum": "" 699 | }, 700 | "require": { 701 | "ext-curl": "*", 702 | "ext-json": "*", 703 | "guzzlehttp/guzzle": "^7.2", 704 | "paragonie/sodium_compat": "^1.6", 705 | "php": "^7.3|^8.0", 706 | "psr/log": "^1.0|^2.0|^3.0" 707 | }, 708 | "require-dev": { 709 | "overtrue/phplint": "^2.3", 710 | "phpunit/phpunit": "^9.3" 711 | }, 712 | "type": "library", 713 | "extra": { 714 | "branch-alias": { 715 | "dev-master": "5.0-dev" 716 | } 717 | }, 718 | "autoload": { 719 | "psr-4": { 720 | "Pusher\\": "src/" 721 | } 722 | }, 723 | "notification-url": "https://packagist.org/downloads/", 724 | "license": [ 725 | "MIT" 726 | ], 727 | "description": "Library for interacting with the Pusher REST API", 728 | "keywords": [ 729 | "events", 730 | "messaging", 731 | "php-pusher-server", 732 | "publish", 733 | "push", 734 | "pusher", 735 | "real time", 736 | "real-time", 737 | "realtime", 738 | "rest", 739 | "trigger" 740 | ], 741 | "support": { 742 | "issues": "https://github.com/pusher/pusher-http-php/issues", 743 | "source": "https://github.com/pusher/pusher-http-php/tree/7.2.2" 744 | }, 745 | "time": "2022-12-20T19:52:36+00:00" 746 | }, 747 | { 748 | "name": "ralouphie/getallheaders", 749 | "version": "3.0.3", 750 | "source": { 751 | "type": "git", 752 | "url": "https://github.com/ralouphie/getallheaders.git", 753 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 754 | }, 755 | "dist": { 756 | "type": "zip", 757 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 758 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 759 | "shasum": "" 760 | }, 761 | "require": { 762 | "php": ">=5.6" 763 | }, 764 | "require-dev": { 765 | "php-coveralls/php-coveralls": "^2.1", 766 | "phpunit/phpunit": "^5 || ^6.5" 767 | }, 768 | "type": "library", 769 | "autoload": { 770 | "files": [ 771 | "src/getallheaders.php" 772 | ] 773 | }, 774 | "notification-url": "https://packagist.org/downloads/", 775 | "license": [ 776 | "MIT" 777 | ], 778 | "authors": [ 779 | { 780 | "name": "Ralph Khattar", 781 | "email": "ralph.khattar@gmail.com" 782 | } 783 | ], 784 | "description": "A polyfill for getallheaders.", 785 | "support": { 786 | "issues": "https://github.com/ralouphie/getallheaders/issues", 787 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 788 | }, 789 | "time": "2019-03-08T08:55:37+00:00" 790 | }, 791 | { 792 | "name": "symfony/deprecation-contracts", 793 | "version": "v3.2.0", 794 | "source": { 795 | "type": "git", 796 | "url": "https://github.com/symfony/deprecation-contracts.git", 797 | "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" 798 | }, 799 | "dist": { 800 | "type": "zip", 801 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", 802 | "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", 803 | "shasum": "" 804 | }, 805 | "require": { 806 | "php": ">=8.1" 807 | }, 808 | "type": "library", 809 | "extra": { 810 | "branch-alias": { 811 | "dev-main": "3.3-dev" 812 | }, 813 | "thanks": { 814 | "name": "symfony/contracts", 815 | "url": "https://github.com/symfony/contracts" 816 | } 817 | }, 818 | "autoload": { 819 | "files": [ 820 | "function.php" 821 | ] 822 | }, 823 | "notification-url": "https://packagist.org/downloads/", 824 | "license": [ 825 | "MIT" 826 | ], 827 | "authors": [ 828 | { 829 | "name": "Nicolas Grekas", 830 | "email": "p@tchwork.com" 831 | }, 832 | { 833 | "name": "Symfony Community", 834 | "homepage": "https://symfony.com/contributors" 835 | } 836 | ], 837 | "description": "A generic function and convention to trigger deprecation notices", 838 | "homepage": "https://symfony.com", 839 | "support": { 840 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" 841 | }, 842 | "funding": [ 843 | { 844 | "url": "https://symfony.com/sponsor", 845 | "type": "custom" 846 | }, 847 | { 848 | "url": "https://github.com/fabpot", 849 | "type": "github" 850 | }, 851 | { 852 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 853 | "type": "tidelift" 854 | } 855 | ], 856 | "time": "2022-11-25T10:21:52+00:00" 857 | } 858 | ], 859 | "packages-dev": [], 860 | "aliases": [], 861 | "minimum-stability": "dev", 862 | "stability-flags": [], 863 | "prefer-stable": true, 864 | "prefer-lowest": false, 865 | "platform": [], 866 | "platform-dev": [], 867 | "plugin-api-version": "2.0.0" 868 | } 869 | -------------------------------------------------------------------------------- /src/ChatifyMessenger.php: -------------------------------------------------------------------------------- 1 | pusher = new Pusher( 29 | config('chatify.pusher.key'), 30 | config('chatify.pusher.secret'), 31 | config('chatify.pusher.app_id'), 32 | config('chatify.pusher.options'), 33 | ); 34 | } 35 | /** 36 | * This method returns the allowed image extensions 37 | * to attach with the message. 38 | * 39 | * @return array 40 | */ 41 | public function getAllowedImages() 42 | { 43 | return config('chatify.attachments.allowed_images'); 44 | } 45 | 46 | /** 47 | * This method returns the allowed file extensions 48 | * to attach with the message. 49 | * 50 | * @return array 51 | */ 52 | public function getAllowedFiles() 53 | { 54 | return config('chatify.attachments.allowed_files'); 55 | } 56 | 57 | /** 58 | * Returns an array contains messenger's colors 59 | * 60 | * @return array 61 | */ 62 | public function getMessengerColors() 63 | { 64 | return config('chatify.colors'); 65 | } 66 | 67 | /** 68 | * Returns a fallback primary color. 69 | * 70 | * @return array 71 | */ 72 | public function getFallbackColor() 73 | { 74 | $colors = $this->getMessengerColors(); 75 | return count($colors) > 0 ? $colors[0] : '#000000'; 76 | } 77 | 78 | /** 79 | * Trigger an event using Pusher 80 | * 81 | * @param string $channel 82 | * @param string $event 83 | * @param array $data 84 | * @return void 85 | */ 86 | public function push($channel, $event, $data) 87 | { 88 | return $this->pusher->trigger($channel, $event, $data); 89 | } 90 | 91 | /** 92 | * Authentication for pusher 93 | * 94 | * @param User $requestUser 95 | * @param User $authUser 96 | * @param string $channelName 97 | * @param string $socket_id 98 | * @param array $data 99 | * @return void 100 | */ 101 | public function pusherAuth($requestUser, $authUser, $channelName, $socket_id) 102 | { 103 | // Auth data 104 | $authData = json_encode([ 105 | 'user_id' => $authUser->id, 106 | 'user_info' => [ 107 | 'name' => $authUser->name 108 | ] 109 | ]); 110 | // check if user authenticated 111 | if (Auth::check()) { 112 | if($requestUser->id == $authUser->id){ 113 | return $this->pusher->socket_auth( 114 | $channelName, 115 | $socket_id, 116 | $authData 117 | ); 118 | } 119 | // if not authorized 120 | return response()->json(['message'=>'Unauthorized'], 401); 121 | } 122 | // if not authenticated 123 | return response()->json(['message'=>'Not authenticated'], 403); 124 | } 125 | 126 | /** 127 | * Fetch & parse message and return the message card 128 | * view as a response. 129 | * 130 | * @param Message $prefetchedMessage 131 | * @param int $id 132 | * @return array 133 | */ 134 | public function parseMessage($prefetchedMessage = null, $id = null) 135 | { 136 | $msg = null; 137 | $attachment = null; 138 | $attachment_type = null; 139 | $attachment_title = null; 140 | if (!!$prefetchedMessage) { 141 | $msg = $prefetchedMessage; 142 | } else { 143 | $msg = Message::where('id', $id)->first(); 144 | if(!$msg){ 145 | return []; 146 | } 147 | } 148 | if (isset($msg->attachment)) { 149 | $attachmentOBJ = json_decode($msg->attachment); 150 | $attachment = $attachmentOBJ->new_name; 151 | $attachment_title = htmlentities(trim($attachmentOBJ->old_name), ENT_QUOTES, 'UTF-8'); 152 | $ext = pathinfo($attachment, PATHINFO_EXTENSION); 153 | $attachment_type = in_array($ext, $this->getAllowedImages()) ? 'image' : 'file'; 154 | } 155 | return [ 156 | 'id' => $msg->id, 157 | 'from_id' => $msg->from_id, 158 | 'to_id' => $msg->to_id, 159 | 'message' => $msg->body, 160 | 'attachment' => (object) [ 161 | 'file' => $attachment, 162 | 'title' => $attachment_title, 163 | 'type' => $attachment_type 164 | ], 165 | 'timeAgo' => $msg->created_at->diffForHumans(), 166 | 'created_at' => $msg->created_at->toIso8601String(), 167 | 'isSender' => ($msg->from_id == Auth::user()->id), 168 | 'seen' => $msg->seen, 169 | ]; 170 | } 171 | 172 | /** 173 | * Return a message card with the given data. 174 | * 175 | * @param Message $data 176 | * @param boolean $isSender 177 | * @return string 178 | */ 179 | public function messageCard($data, $renderDefaultCard = false) 180 | { 181 | if (!$data) { 182 | return ''; 183 | } 184 | if($renderDefaultCard) { 185 | $data['isSender'] = false; 186 | } 187 | return view('Chatify::layouts.messageCard', $data)->render(); 188 | } 189 | 190 | /** 191 | * Default fetch messages query between a Sender and Receiver. 192 | * 193 | * @param int $user_id 194 | * @return Message|\Illuminate\Database\Eloquent\Builder 195 | */ 196 | public function fetchMessagesQuery($user_id) 197 | { 198 | return Message::where('from_id', Auth::user()->id)->where('to_id', $user_id) 199 | ->orWhere('from_id', $user_id)->where('to_id', Auth::user()->id); 200 | } 201 | 202 | /** 203 | * create a new message to database 204 | * 205 | * @param array $data 206 | * @return Message 207 | */ 208 | public function newMessage($data) 209 | { 210 | $message = new Message(); 211 | $message->from_id = $data['from_id']; 212 | $message->to_id = $data['to_id']; 213 | $message->body = $data['body']; 214 | $message->attachment = $data['attachment']; 215 | $message->save(); 216 | return $message; 217 | } 218 | 219 | /** 220 | * Make messages between the sender [Auth user] and 221 | * the receiver [User id] as seen. 222 | * 223 | * @param int $user_id 224 | * @return bool 225 | */ 226 | public function makeSeen($user_id) 227 | { 228 | Message::Where('from_id', $user_id) 229 | ->where('to_id', Auth::user()->id) 230 | ->where('seen', 0) 231 | ->update(['seen' => 1]); 232 | return 1; 233 | } 234 | 235 | /** 236 | * Get last message for a specific user 237 | * 238 | * @param int $user_id 239 | * @return Message|Collection|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null 240 | */ 241 | public function getLastMessageQuery($user_id) 242 | { 243 | return $this->fetchMessagesQuery($user_id)->latest()->first(); 244 | } 245 | 246 | /** 247 | * Count Unseen messages 248 | * 249 | * @param int $user_id 250 | * @return Collection 251 | */ 252 | public function countUnseenMessages($user_id) 253 | { 254 | return Message::where('from_id', $user_id)->where('to_id', Auth::user()->id)->where('seen', 0)->count(); 255 | } 256 | 257 | /** 258 | * Get user list's item data [Contact Itme] 259 | * (e.g. User data, Last message, Unseen Counter...) 260 | * 261 | * @param int $messenger_id 262 | * @param Collection $user 263 | * @return string 264 | */ 265 | public function getContactItem($user) 266 | { 267 | try { 268 | // get last message 269 | $lastMessage = $this->getLastMessageQuery($user->id); 270 | // Get Unseen messages counter 271 | $unseenCounter = $this->countUnseenMessages($user->id); 272 | if ($lastMessage) { 273 | $lastMessage->created_at = $lastMessage->created_at->toIso8601String(); 274 | $lastMessage->timeAgo = $lastMessage->created_at->diffForHumans(); 275 | } 276 | return view('Chatify::layouts.listItem', [ 277 | 'get' => 'users', 278 | 'user' => $this->getUserWithAvatar($user), 279 | 'lastMessage' => $lastMessage, 280 | 'unseenCounter' => $unseenCounter, 281 | ])->render(); 282 | } catch (\Throwable $th) { 283 | throw new Exception($th->getMessage()); 284 | } 285 | } 286 | 287 | /** 288 | * Get user with avatar (formatted). 289 | * 290 | * @param Collection $user 291 | * @return Collection 292 | */ 293 | public function getUserWithAvatar($user) 294 | { 295 | if ($user->avatar == 'avatar.png' && config('chatify.gravatar.enabled')) { 296 | $imageSize = config('chatify.gravatar.image_size'); 297 | $imageset = config('chatify.gravatar.imageset'); 298 | $user->avatar = 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($user->email))) . '?s=' . $imageSize . '&d=' . $imageset; 299 | } else { 300 | $user->avatar = self::getUserAvatarUrl($user->avatar); 301 | } 302 | return $user; 303 | } 304 | 305 | /** 306 | * Check if a user in the favorite list 307 | * 308 | * @param int $user_id 309 | * @return boolean 310 | */ 311 | public function inFavorite($user_id) 312 | { 313 | return Favorite::where('user_id', Auth::user()->id) 314 | ->where('favorite_id', $user_id)->count() > 0 315 | ? true : false; 316 | } 317 | 318 | /** 319 | * Make user in favorite list 320 | * 321 | * @param int $user_id 322 | * @param int $star 323 | * @return boolean 324 | */ 325 | public function makeInFavorite($user_id, $action) 326 | { 327 | if ($action > 0) { 328 | // Star 329 | $star = new Favorite(); 330 | $star->user_id = Auth::user()->id; 331 | $star->favorite_id = $user_id; 332 | $star->save(); 333 | return $star ? true : false; 334 | } else { 335 | // UnStar 336 | $star = Favorite::where('user_id', Auth::user()->id)->where('favorite_id', $user_id)->delete(); 337 | return $star ? true : false; 338 | } 339 | } 340 | 341 | /** 342 | * Get shared photos of the conversation 343 | * 344 | * @param int $user_id 345 | * @return array 346 | */ 347 | public function getSharedPhotos($user_id) 348 | { 349 | $images = array(); // Default 350 | // Get messages 351 | $msgs = $this->fetchMessagesQuery($user_id)->orderBy('created_at', 'DESC'); 352 | if ($msgs->count() > 0) { 353 | foreach ($msgs->get() as $msg) { 354 | // If message has attachment 355 | if ($msg->attachment) { 356 | $attachment = json_decode($msg->attachment); 357 | // determine the type of the attachment 358 | in_array(pathinfo($attachment->new_name, PATHINFO_EXTENSION), $this->getAllowedImages()) 359 | ? array_push($images, $attachment->new_name) : ''; 360 | } 361 | } 362 | } 363 | return $images; 364 | } 365 | 366 | /** 367 | * Delete Conversation 368 | * 369 | * @param int $user_id 370 | * @return boolean 371 | */ 372 | public function deleteConversation($user_id) 373 | { 374 | try { 375 | foreach ($this->fetchMessagesQuery($user_id)->get() as $msg) { 376 | // delete file attached if exist 377 | if (isset($msg->attachment)) { 378 | $path = config('chatify.attachments.folder').'/'.json_decode($msg->attachment)->new_name; 379 | if (self::storage()->exists($path)) { 380 | self::storage()->delete($path); 381 | } 382 | } 383 | // delete from database 384 | $msg->delete(); 385 | } 386 | return 1; 387 | } catch (Exception $e) { 388 | return 0; 389 | } 390 | } 391 | 392 | /** 393 | * Delete message by ID 394 | * 395 | * @param int $id 396 | * @return boolean 397 | */ 398 | public function deleteMessage($id) 399 | { 400 | try { 401 | $msg = Message::where('from_id', auth()->id())->where('id', $id)->firstOrFail(); 402 | if (isset($msg->attachment)) { 403 | $path = config('chatify.attachments.folder') . '/' . json_decode($msg->attachment)->new_name; 404 | if (self::storage()->exists($path)) { 405 | self::storage()->delete($path); 406 | } 407 | } 408 | $msg->delete(); 409 | return 1; 410 | } catch (Exception $e) { 411 | throw new Exception($e->getMessage()); 412 | } 413 | } 414 | 415 | /** 416 | * Return a storage instance with disk name specified in the config. 417 | * 418 | */ 419 | public function storage() 420 | { 421 | return Storage::disk(config('chatify.storage_disk_name')); 422 | } 423 | 424 | /** 425 | * Get user avatar url. 426 | * 427 | * @param string $user_avatar_name 428 | * @return string 429 | */ 430 | public function getUserAvatarUrl($user_avatar_name) 431 | { 432 | return self::storage()->url(config('chatify.user_avatar.folder') . '/' . $user_avatar_name); 433 | } 434 | 435 | /** 436 | * Get attachment's url. 437 | * 438 | * @param string $attachment_name 439 | * @return string 440 | */ 441 | public function getAttachmentUrl($attachment_name) 442 | { 443 | return self::storage()->url(config('chatify.attachments.folder') . '/' . $attachment_name); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/ChatifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | bind('ChatifyMessenger', function () { 20 | return new \Chatify\ChatifyMessenger; 21 | }); 22 | } 23 | 24 | /** 25 | * Bootstrap services. 26 | * 27 | * @return void 28 | */ 29 | public function boot() 30 | { 31 | // Load Views and Routes 32 | $this->loadViewsFrom(__DIR__ . '/views', 'Chatify'); 33 | $this->loadRoutes(); 34 | 35 | if ($this->app->runningInConsole()) { 36 | $this->commands([ 37 | InstallCommand::class, 38 | PublishCommand::class, 39 | ]); 40 | $this->setPublishes(); 41 | } 42 | } 43 | 44 | /** 45 | * Publishing the files that the user may override. 46 | * 47 | * @return void 48 | */ 49 | protected function setPublishes() 50 | { 51 | // Load user's avatar folder from package's config 52 | $userAvatarFolder = json_decode(json_encode(include(__DIR__.'/config/chatify.php')))->user_avatar->folder; 53 | 54 | // Config 55 | $this->publishes([ 56 | __DIR__ . '/config/chatify.php' => config_path('chatify.php') 57 | ], 'chatify-config'); 58 | 59 | // Migrations 60 | $this->publishes([ 61 | __DIR__ . '/database/migrations/2022_01_10_99999_add_active_status_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_active_status_to_users.php'), 62 | __DIR__ . '/database/migrations/2022_01_10_99999_add_avatar_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_avatar_to_users.php'), 63 | __DIR__ . '/database/migrations/2022_01_10_99999_add_dark_mode_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_dark_mode_to_users.php'), 64 | __DIR__ . '/database/migrations/2022_01_10_99999_add_messenger_color_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_messenger_color_to_users.php'), 65 | __DIR__ . '/database/migrations/2022_01_10_99999_create_chatify_favorites_table.php' => database_path('migrations/' . date('Y_m_d') . '_999999_create_chatify_favorites_table.php'), 66 | __DIR__ . '/database/migrations/2022_01_10_99999_create_chatify_messages_table.php' => database_path('migrations/' . date('Y_m_d') . '_999999_create_chatify_messages_table.php'), 67 | ], 'chatify-migrations'); 68 | 69 | // Models 70 | $isV8 = explode('.', app()->version())[0] >= 8; 71 | $this->publishes([ 72 | __DIR__ . '/Models' => app_path($isV8 ? 'Models' : '') 73 | ], 'chatify-models'); 74 | 75 | // Controllers 76 | $this->publishes([ 77 | __DIR__ . '/Http/Controllers' => app_path('Http/Controllers/vendor/Chatify') 78 | ], 'chatify-controllers'); 79 | 80 | // Views 81 | $this->publishes([ 82 | __DIR__ . '/views' => resource_path('views/vendor/Chatify') 83 | ], 'chatify-views'); 84 | 85 | // Assets 86 | $this->publishes([ 87 | // CSS 88 | __DIR__ . '/assets/css' => public_path('css/chatify'), 89 | // JavaScript 90 | __DIR__ . '/assets/js' => public_path('js/chatify'), 91 | // Images 92 | __DIR__ . '/assets/imgs' => storage_path('app/public/' . $userAvatarFolder), 93 | // CSS 94 | __DIR__ . '/assets/sounds' => public_path('sounds/chatify'), 95 | ], 'chatify-assets'); 96 | 97 | // Routes (API and Web) 98 | $this->publishes([ 99 | __DIR__ . '/routes' => base_path('routes/chatify') 100 | ], 'chatify-routes'); 101 | } 102 | 103 | /** 104 | * Group the routes and set up configurations to load them. 105 | * 106 | * @return void 107 | */ 108 | protected function loadRoutes() 109 | { 110 | if (config('chatify.routes.custom')) { 111 | Route::group($this->routesConfigurations(), function () { 112 | $this->loadRoutesFrom(base_path('routes/chatify/web.php')); 113 | }); 114 | Route::group($this->apiRoutesConfigurations(), function () { 115 | $this->loadRoutesFrom(base_path('routes/chatify/api.php')); 116 | }); 117 | } else { 118 | Route::group($this->routesConfigurations(), function () { 119 | $this->loadRoutesFrom(__DIR__ . '/routes/web.php'); 120 | }); 121 | Route::group($this->apiRoutesConfigurations(), function () { 122 | $this->loadRoutesFrom(__DIR__ . '/routes/api.php'); 123 | }); 124 | } 125 | } 126 | 127 | /** 128 | * Routes configurations. 129 | * 130 | * @return array 131 | */ 132 | private function routesConfigurations() 133 | { 134 | return [ 135 | 'prefix' => config('chatify.routes.prefix'), 136 | 'namespace' => config('chatify.routes.namespace'), 137 | 'middleware' => config('chatify.routes.middleware'), 138 | ]; 139 | } 140 | /** 141 | * API routes configurations. 142 | * 143 | * @return array 144 | */ 145 | private function apiRoutesConfigurations() 146 | { 147 | return [ 148 | 'prefix' => config('chatify.api_routes.prefix'), 149 | 'namespace' => config('chatify.api_routes.namespace'), 150 | 'middleware' => config('chatify.api_routes.middleware'), 151 | ]; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Console/InstallCommand.php: -------------------------------------------------------------------------------- 1 | isV8 = explode('.',app()->version())[0] >= 8; 40 | 41 | $this->info('Installing Chatify...'); 42 | 43 | $this->line('----------'); 44 | $this->line('Configurations...'); 45 | $this->modifyModelsPath('/../Http/Controllers/MessagesController.php','User'); 46 | $this->modifyModelsPath('/../Http/Controllers/MessagesController.php','ChFavorite'); 47 | $this->modifyModelsPath('/../Http/Controllers/MessagesController.php','ChMessage'); 48 | $this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','User'); 49 | $this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','ChFavorite'); 50 | $this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','ChMessage'); 51 | $this->modifyModelsPath('/../ChatifyMessenger.php','ChFavorite'); 52 | $this->modifyModelsPath('/../ChatifyMessenger.php','ChMessage'); 53 | $this->modifyModelsPath('/../Models/ChFavorite.php'); 54 | $this->modifyModelsPath('/../Models/ChMessage.php'); 55 | $this->info('[✓] done'); 56 | 57 | $assetsToBePublished = [ 58 | 'config' => config_path('chatify.php'), 59 | 'views' => resource_path('views/vendor/Chatify'), 60 | 'assets' => public_path('css/chatify'), 61 | 'models' => app_path(($this->isV8 ? 'Models/' : '').'ChMessage.php'), 62 | 'migrations' => database_path('migrations/2019_09_22_192348_create_messages_table.php'), 63 | 'routes' => base_path('routes/chatify'), 64 | ]; 65 | 66 | foreach ($assetsToBePublished as $target => $path) { 67 | $this->line('----------'); 68 | $this->process($target, $path); 69 | } 70 | 71 | $this->line('----------'); 72 | $this->line('Creating storage symlink...'); 73 | Artisan::call('storage:link'); 74 | $this->info('[✓] Storage linked.'); 75 | 76 | $this->line('----------'); 77 | $this->info('[✓] Chatify installed successfully'); 78 | } 79 | 80 | /** 81 | * Modify models imports/namespace path according to Laravel version. 82 | * 83 | * @param string $targetFilePath 84 | * @param string $model 85 | * @return void 86 | */ 87 | private function modifyModelsPath($targetFilePath, $model = null){ 88 | $path = realpath(__DIR__.$targetFilePath); 89 | $contents = File::get($path); 90 | $model = !empty($model) ? '\\'.$model : ';'; 91 | $contents = str_replace( 92 | (!$this->isV8 ? 'App\Models' : 'App').$model, 93 | ($this->isV8 ? 'App\Models' : 'App').$model, 94 | $contents 95 | ); 96 | File::put($path, $contents); 97 | } 98 | 99 | /** 100 | * Check, publish, or overwrite the assets. 101 | * 102 | * @param string $target 103 | * @param string $path 104 | * @return void 105 | */ 106 | private function process($target, $path) 107 | { 108 | $this->line('Publishing '.$target.'...'); 109 | if (!File::exists($path)) { 110 | $this->publish($target); 111 | $this->info('[✓] '.$target.' published.'); 112 | return; 113 | } 114 | if ($this->shouldOverwrite($target)) { 115 | $this->line('Overwriting '.$target.'...'); 116 | $this->publish($target,true); 117 | $this->info('[✓] '.$target.' published.'); 118 | return; 119 | } 120 | $this->line('[-] Ignored, The existing '.$target.' was not overwritten'); 121 | } 122 | 123 | /** 124 | * Ask to overwrite. 125 | * 126 | * @param string $target 127 | * @return void 128 | */ 129 | private function shouldOverwrite($target) 130 | { 131 | return $this->confirm( 132 | $target.' already exists. Do you want to overwrite it?', 133 | false 134 | ); 135 | } 136 | 137 | /** 138 | * Call the publish command. 139 | * 140 | * @param string $tag 141 | * @param bool $forcePublish 142 | * @return void 143 | */ 144 | private function publish($tag, $forcePublish = false) 145 | { 146 | $this->call('vendor:publish', [ 147 | '--tag' => 'chatify-'.$tag, 148 | '--force' => $forcePublish, 149 | ]); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Console/PublishCommand.php: -------------------------------------------------------------------------------- 1 | option('force')){ 31 | $this->call('vendor:publish', [ 32 | '--tag' => 'chatify-config', 33 | '--force' => true, 34 | ]); 35 | 36 | $this->call('vendor:publish', [ 37 | '--tag' => 'chatify-migrations', 38 | '--force' => true, 39 | ]); 40 | 41 | $this->call('vendor:publish', [ 42 | '--tag' => 'chatify-models', 43 | '--force' => true, 44 | ]); 45 | } 46 | 47 | $this->call('vendor:publish', [ 48 | '--tag' => 'chatify-views', 49 | '--force' => true, 50 | ]); 51 | 52 | $this->call('vendor:publish', [ 53 | '--tag' => 'chatify-assets', 54 | '--force' => true, 55 | ]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Facades/ChatifyMessenger.php: -------------------------------------------------------------------------------- 1 | user(), 32 | Auth::user(), 33 | $request['channel_name'], 34 | $request['socket_id'] 35 | ); 36 | } 37 | 38 | /** 39 | * Fetch data by id for (user/group) 40 | * 41 | * @param Request $request 42 | * @return \Illuminate\Http\JsonResponse 43 | */ 44 | public function idFetchData(Request $request) 45 | { 46 | return auth()->user(); 47 | // Favorite 48 | $favorite = Chatify::inFavorite($request['id']); 49 | 50 | // User data 51 | if ($request['type'] == 'user') { 52 | $fetch = User::where('id', $request['id'])->first(); 53 | if($fetch){ 54 | $userAvatar = Chatify::getUserWithAvatar($fetch)->avatar; 55 | } 56 | } 57 | 58 | // send the response 59 | return Response::json([ 60 | 'favorite' => $favorite, 61 | 'fetch' => $fetch ?? null, 62 | 'user_avatar' => $userAvatar ?? null, 63 | ]); 64 | } 65 | 66 | /** 67 | * This method to make a links for the attachments 68 | * to be downloadable. 69 | * 70 | * @param string $fileName 71 | * @return \Illuminate\Http\JsonResponse 72 | */ 73 | public function download($fileName) 74 | { 75 | $path = config('chatify.attachments.folder') . '/' . $fileName; 76 | if (Chatify::storage()->exists($path)) { 77 | return response()->json([ 78 | 'file_name' => $fileName, 79 | 'download_path' => Chatify::storage()->url($path) 80 | ], 200); 81 | } else { 82 | return response()->json([ 83 | 'message'=>"Sorry, File does not exist in our server or may have been deleted!" 84 | ], 404); 85 | } 86 | } 87 | 88 | /** 89 | * Send a message to database 90 | * 91 | * @param Request $request 92 | * @return JSON response 93 | */ 94 | public function send(Request $request) 95 | { 96 | // default variables 97 | $error = (object)[ 98 | 'status' => 0, 99 | 'message' => null 100 | ]; 101 | $attachment = null; 102 | $attachment_title = null; 103 | 104 | // if there is attachment [file] 105 | if ($request->hasFile('file')) { 106 | // allowed extensions 107 | $allowed_images = Chatify::getAllowedImages(); 108 | $allowed_files = Chatify::getAllowedFiles(); 109 | $allowed = array_merge($allowed_images, $allowed_files); 110 | 111 | $file = $request->file('file'); 112 | // check file size 113 | if ($file->getSize() < Chatify::getMaxUploadSize()) { 114 | if (in_array(strtolower($file->extension()), $allowed)) { 115 | // get attachment name 116 | $attachment_title = $file->getClientOriginalName(); 117 | // upload attachment and store the new name 118 | $attachment = Str::uuid() . "." . $file->extension(); 119 | $file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name')); 120 | } else { 121 | $error->status = 1; 122 | $error->message = "File extension not allowed!"; 123 | } 124 | } else { 125 | $error->status = 1; 126 | $error->message = "File size you are trying to upload is too large!"; 127 | } 128 | } 129 | 130 | if (!$error->status) { 131 | // send to database 132 | $message = Chatify::newMessage([ 133 | 'type' => $request['type'], 134 | 'from_id' => Auth::user()->id, 135 | 'to_id' => $request['id'], 136 | 'body' => htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'), 137 | 'attachment' => ($attachment) ? json_encode((object)[ 138 | 'new_name' => $attachment, 139 | 'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'), 140 | ]) : null, 141 | ]); 142 | 143 | // fetch message to send it with the response 144 | $messageData = Chatify::parseMessage($message); 145 | 146 | // send to user using pusher 147 | if (Auth::user()->id != $request['id']) { 148 | Chatify::push("private-chatify.".$request['id'], 'messaging', [ 149 | 'from_id' => Auth::user()->id, 150 | 'to_id' => $request['id'], 151 | 'message' => $messageData 152 | ]); 153 | } 154 | } 155 | 156 | // send the response 157 | return Response::json([ 158 | 'status' => '200', 159 | 'error' => $error, 160 | 'message' => $messageData ?? [], 161 | 'tempID' => $request['temporaryMsgId'], 162 | ]); 163 | } 164 | 165 | /** 166 | * fetch [user/group] messages from database 167 | * 168 | * @param Request $request 169 | * @return JSON response 170 | */ 171 | public function fetch(Request $request) 172 | { 173 | $query = Chatify::fetchMessagesQuery($request['id'])->latest(); 174 | $messages = $query->paginate($request->per_page ?? $this->perPage); 175 | $totalMessages = $messages->total(); 176 | $lastPage = $messages->lastPage(); 177 | $response = [ 178 | 'total' => $totalMessages, 179 | 'last_page' => $lastPage, 180 | 'last_message_id' => collect($messages->items())->last()->id ?? null, 181 | 'messages' => $messages->items(), 182 | ]; 183 | return Response::json($response); 184 | } 185 | 186 | /** 187 | * Make messages as seen 188 | * 189 | * @param Request $request 190 | * @return void 191 | */ 192 | public function seen(Request $request) 193 | { 194 | // make as seen 195 | $seen = Chatify::makeSeen($request['id']); 196 | // send the response 197 | return Response::json([ 198 | 'status' => $seen, 199 | ], 200); 200 | } 201 | 202 | /** 203 | * Get contacts list 204 | * 205 | * @param Request $request 206 | * @return \Illuminate\Http\JsonResponse response 207 | */ 208 | public function getContacts(Request $request) 209 | { 210 | // get all users that received/sent message from/to [Auth user] 211 | $users = Message::join('users', function ($join) { 212 | $join->on('ch_messages.from_id', '=', 'users.id') 213 | ->orOn('ch_messages.to_id', '=', 'users.id'); 214 | }) 215 | ->where(function ($q) { 216 | $q->where('ch_messages.from_id', Auth::user()->id) 217 | ->orWhere('ch_messages.to_id', Auth::user()->id); 218 | }) 219 | ->where('users.id','!=',Auth::user()->id) 220 | ->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at')) 221 | ->orderBy('max_created_at', 'desc') 222 | ->groupBy('users.id') 223 | ->paginate($request->per_page ?? $this->perPage); 224 | 225 | return response()->json([ 226 | 'contacts' => $users->items(), 227 | 'total' => $users->total() ?? 0, 228 | 'last_page' => $users->lastPage() ?? 1, 229 | ], 200); 230 | } 231 | 232 | /** 233 | * Put a user in the favorites list 234 | * 235 | * @param Request $request 236 | * @return void 237 | */ 238 | public function favorite(Request $request) 239 | { 240 | $userId = $request['user_id']; 241 | // check action [star/unstar] 242 | $favoriteStatus = Chatify::inFavorite($userId) ? 0 : 1; 243 | Chatify::makeInFavorite($userId, $favoriteStatus); 244 | 245 | // send the response 246 | return Response::json([ 247 | 'status' => @$favoriteStatus, 248 | ], 200); 249 | } 250 | 251 | /** 252 | * Get favorites list 253 | * 254 | * @param Request $request 255 | * @return void 256 | */ 257 | public function getFavorites(Request $request) 258 | { 259 | $favorites = Favorite::where('user_id', Auth::user()->id)->get(); 260 | foreach ($favorites as $favorite) { 261 | $favorite->user = User::where('id', $favorite->favorite_id)->first(); 262 | } 263 | return Response::json([ 264 | 'total' => count($favorites), 265 | 'favorites' => $favorites ?? [], 266 | ], 200); 267 | } 268 | 269 | /** 270 | * Search in messenger 271 | * 272 | * @param Request $request 273 | * @return \Illuminate\Http\JsonResponse 274 | */ 275 | public function search(Request $request) 276 | { 277 | $input = trim(filter_var($request['input'])); 278 | $records = User::where('id','!=',Auth::user()->id) 279 | ->where('name', 'LIKE', "%{$input}%") 280 | ->paginate($request->per_page ?? $this->perPage); 281 | 282 | foreach ($records->items() as $index => $record) { 283 | $records[$index] += Chatify::getUserWithAvatar($record); 284 | } 285 | 286 | return Response::json([ 287 | 'records' => $records->items(), 288 | 'total' => $records->total(), 289 | 'last_page' => $records->lastPage() 290 | ], 200); 291 | } 292 | 293 | /** 294 | * Get shared photos 295 | * 296 | * @param Request $request 297 | * @return \Illuminate\Http\JsonResponse 298 | */ 299 | public function sharedPhotos(Request $request) 300 | { 301 | $images = Chatify::getSharedPhotos($request['user_id']); 302 | 303 | foreach ($images as $image) { 304 | $image = asset(config('chatify.attachments.folder') . $image); 305 | } 306 | // send the response 307 | return Response::json([ 308 | 'shared' => $images ?? [], 309 | ], 200); 310 | } 311 | 312 | /** 313 | * Delete conversation 314 | * 315 | * @param Request $request 316 | * @return void 317 | */ 318 | public function deleteConversation(Request $request) 319 | { 320 | // delete 321 | $delete = Chatify::deleteConversation($request['id']); 322 | 323 | // send the response 324 | return Response::json([ 325 | 'deleted' => $delete ? 1 : 0, 326 | ], 200); 327 | } 328 | 329 | public function updateSettings(Request $request) 330 | { 331 | $msg = null; 332 | $error = $success = 0; 333 | 334 | // dark mode 335 | if ($request['dark_mode']) { 336 | $request['dark_mode'] == "dark" 337 | ? User::where('id', Auth::user()->id)->update(['dark_mode' => 1]) // Make Dark 338 | : User::where('id', Auth::user()->id)->update(['dark_mode' => 0]); // Make Light 339 | } 340 | 341 | // If messenger color selected 342 | if ($request['messengerColor']) { 343 | $messenger_color = trim(filter_var($request['messengerColor'])); 344 | User::where('id', Auth::user()->id) 345 | ->update(['messenger_color' => $messenger_color]); 346 | } 347 | // if there is a [file] 348 | if ($request->hasFile('avatar')) { 349 | // allowed extensions 350 | $allowed_images = Chatify::getAllowedImages(); 351 | 352 | $file = $request->file('avatar'); 353 | // check file size 354 | if ($file->getSize() < Chatify::getMaxUploadSize()) { 355 | if (in_array(strtolower($file->extension()), $allowed_images)) { 356 | // delete the older one 357 | if (Auth::user()->avatar != config('chatify.user_avatar.default')) { 358 | $path = Chatify::getUserAvatarUrl(Auth::user()->avatar); 359 | if (Chatify::storage()->exists($path)) { 360 | Chatify::storage()->delete($path); 361 | } 362 | } 363 | // upload 364 | $avatar = Str::uuid() . "." . $file->extension(); 365 | $update = User::where('id', Auth::user()->id)->update(['avatar' => $avatar]); 366 | $file->storeAs(config('chatify.user_avatar.folder'), $avatar, config('chatify.storage_disk_name')); 367 | $success = $update ? 1 : 0; 368 | } else { 369 | $msg = "File extension not allowed!"; 370 | $error = 1; 371 | } 372 | } else { 373 | $msg = "File size you are trying to upload is too large!"; 374 | $error = 1; 375 | } 376 | } 377 | 378 | // send the response 379 | return Response::json([ 380 | 'status' => $success ? 1 : 0, 381 | 'error' => $error ? 1 : 0, 382 | 'message' => $error ? $msg : 0, 383 | ], 200); 384 | } 385 | 386 | /** 387 | * Set user's active status 388 | * 389 | * @param Request $request 390 | * @return void 391 | */ 392 | public function setActiveStatus(Request $request) 393 | { 394 | $activeStatus = $request['status'] > 0 ? 1 : 0; 395 | $status = User::where('id', Auth::user()->id)->update(['active_status' => $activeStatus]); 396 | return Response::json([ 397 | 'status' => $status, 398 | ], 200); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/Http/Controllers/MessagesController.php: -------------------------------------------------------------------------------- 1 | user(), 31 | Auth::user(), 32 | $request['channel_name'], 33 | $request['socket_id'] 34 | ); 35 | } 36 | 37 | /** 38 | * Returning the view of the app with the required data. 39 | * 40 | * @param int $id 41 | * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View 42 | */ 43 | public function index( $id = null) 44 | { 45 | $messenger_color = Auth::user()->messenger_color; 46 | return view('Chatify::pages.app', [ 47 | 'id' => $id ?? 0, 48 | 'messengerColor' => $messenger_color ? $messenger_color : Chatify::getFallbackColor(), 49 | 'dark_mode' => Auth::user()->dark_mode < 1 ? 'light' : 'dark', 50 | ]); 51 | } 52 | 53 | 54 | /** 55 | * Fetch data (user, favorite.. etc). 56 | * 57 | * @param Request $request 58 | * @return JsonResponse 59 | */ 60 | public function idFetchData(Request $request) 61 | { 62 | $favorite = Chatify::inFavorite($request['id']); 63 | $fetch = User::where('id', $request['id'])->select('id', 'name', 'email')->first(); 64 | if($fetch){ 65 | $userAvatar = Chatify::getUserWithAvatar($fetch)->avatar; 66 | } 67 | unset($fetch['email']); 68 | return Response::json([ 69 | 'favorite' => $favorite, 70 | 'fetch' => $fetch ?? null, 71 | 'user_avatar' => $userAvatar ?? null, 72 | ]); 73 | } 74 | 75 | /** 76 | * This method to make a links for the attachments 77 | * to be downloadable. 78 | * 79 | * @param string $fileName 80 | * @return \Symfony\Component\HttpFoundation\StreamedResponse|void 81 | */ 82 | public function download($fileName) 83 | { 84 | $filePath = config('chatify.attachments.folder') . '/' . $fileName; 85 | if (Chatify::storage()->exists($filePath)) { 86 | return Chatify::storage()->download($filePath); 87 | } 88 | return abort(404, "Sorry, File does not exist in our server or may have been deleted!"); 89 | } 90 | 91 | /** 92 | * Send a message to database 93 | * 94 | * @param Request $request 95 | * @return JsonResponse 96 | */ 97 | public function send(Request $request) 98 | { 99 | // default variables 100 | $error = (object)[ 101 | 'status' => 0, 102 | 'message' => null 103 | ]; 104 | $attachment = null; 105 | $attachment_title = null; 106 | 107 | // if there is attachment [file] 108 | if ($request->hasFile('file')) { 109 | // allowed extensions 110 | $allowed_images = Chatify::getAllowedImages(); 111 | $allowed_files = Chatify::getAllowedFiles(); 112 | $allowed = array_merge($allowed_images, $allowed_files); 113 | 114 | $file = $request->file('file'); 115 | // check file size 116 | if ($file->getSize() < Chatify::getMaxUploadSize()) { 117 | if (in_array(strtolower($file->extension()), $allowed)) { 118 | // get attachment name 119 | $attachment_title = $file->getClientOriginalName(); 120 | // upload attachment and store the new name 121 | $attachment = Str::uuid() . "." . $file->extension(); 122 | $file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name')); 123 | } else { 124 | $error->status = 1; 125 | $error->message = "File extension not allowed!"; 126 | } 127 | } else { 128 | $error->status = 1; 129 | $error->message = "File size you are trying to upload is too large!"; 130 | } 131 | } 132 | 133 | if (!$error->status) { 134 | $message = Chatify::newMessage([ 135 | 'from_id' => Auth::user()->id, 136 | 'to_id' => $request['id'], 137 | 'body' => htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'), 138 | 'attachment' => ($attachment) ? json_encode((object)[ 139 | 'new_name' => $attachment, 140 | 'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'), 141 | ]) : null, 142 | ]); 143 | $messageData = Chatify::parseMessage($message); 144 | if (Auth::user()->id != $request['id']) { 145 | Chatify::push("private-chatify.".$request['id'], 'messaging', [ 146 | 'from_id' => Auth::user()->id, 147 | 'to_id' => $request['id'], 148 | 'message' => Chatify::messageCard($messageData, true) 149 | ]); 150 | } 151 | } 152 | 153 | // send the response 154 | return Response::json([ 155 | 'status' => '200', 156 | 'error' => $error, 157 | 'message' => Chatify::messageCard(@$messageData), 158 | 'tempID' => $request['temporaryMsgId'], 159 | ]); 160 | } 161 | 162 | /** 163 | * fetch [user/group] messages from database 164 | * 165 | * @param Request $request 166 | * @return JsonResponse 167 | */ 168 | public function fetch(Request $request) 169 | { 170 | $query = Chatify::fetchMessagesQuery($request['id'])->latest(); 171 | $messages = $query->paginate($request->per_page ?? $this->perPage); 172 | $totalMessages = $messages->total(); 173 | $lastPage = $messages->lastPage(); 174 | $response = [ 175 | 'total' => $totalMessages, 176 | 'last_page' => $lastPage, 177 | 'last_message_id' => collect($messages->items())->last()->id ?? null, 178 | 'messages' => '', 179 | ]; 180 | 181 | // if there is no messages yet. 182 | if ($totalMessages < 1) { 183 | $response['messages'] ='

Say \'hi\' and start messaging

'; 184 | return Response::json($response); 185 | } 186 | if (count($messages->items()) < 1) { 187 | $response['messages'] = ''; 188 | return Response::json($response); 189 | } 190 | $allMessages = null; 191 | foreach ($messages->reverse() as $message) { 192 | $allMessages .= Chatify::messageCard( 193 | Chatify::parseMessage($message) 194 | ); 195 | } 196 | $response['messages'] = $allMessages; 197 | return Response::json($response); 198 | } 199 | 200 | /** 201 | * Make messages as seen 202 | * 203 | * @param Request $request 204 | * @return JsonResponse|void 205 | */ 206 | public function seen(Request $request) 207 | { 208 | // make as seen 209 | $seen = Chatify::makeSeen($request['id']); 210 | // send the response 211 | return Response::json([ 212 | 'status' => $seen, 213 | ], 200); 214 | } 215 | 216 | /** 217 | * Get contacts list 218 | * 219 | * @param Request $request 220 | * @return JsonResponse 221 | */ 222 | public function getContacts(Request $request) 223 | { 224 | // get all users that received/sent message from/to [Auth user] 225 | $users = Message::join('users', function ($join) { 226 | $join->on('ch_messages.from_id', '=', 'users.id') 227 | ->orOn('ch_messages.to_id', '=', 'users.id'); 228 | }) 229 | ->where(function ($q) { 230 | $q->where('ch_messages.from_id', Auth::user()->id) 231 | ->orWhere('ch_messages.to_id', Auth::user()->id); 232 | }) 233 | ->where('users.id','!=',Auth::user()->id) 234 | ->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at')) 235 | ->orderBy('max_created_at', 'desc') 236 | ->groupBy('users.id') 237 | ->paginate($request->per_page ?? $this->perPage); 238 | 239 | $usersList = $users->items(); 240 | 241 | if (count($usersList) > 0) { 242 | $contacts = ''; 243 | foreach ($usersList as $user) { 244 | $contacts .= Chatify::getContactItem($user); 245 | } 246 | } else { 247 | $contacts = '

Your contact list is empty

'; 248 | } 249 | 250 | return Response::json([ 251 | 'contacts' => $contacts, 252 | 'total' => $users->total() ?? 0, 253 | 'last_page' => $users->lastPage() ?? 1, 254 | ], 200); 255 | } 256 | 257 | /** 258 | * Update user's list item data 259 | * 260 | * @param Request $request 261 | * @return JsonResponse 262 | */ 263 | public function updateContactItem(Request $request) 264 | { 265 | // Get user data 266 | $user = User::where('id', $request['user_id'])->first(); 267 | if(!$user){ 268 | return Response::json([ 269 | 'message' => 'User not found!', 270 | ], 401); 271 | } 272 | $contactItem = Chatify::getContactItem($user); 273 | 274 | // send the response 275 | return Response::json([ 276 | 'contactItem' => $contactItem, 277 | ], 200); 278 | } 279 | 280 | /** 281 | * Put a user in the favorites list 282 | * 283 | * @param Request $request 284 | * @return JsonResponse|void 285 | */ 286 | public function favorite(Request $request) 287 | { 288 | $userId = $request['user_id']; 289 | // check action [star/unstar] 290 | $favoriteStatus = Chatify::inFavorite($userId) ? 0 : 1; 291 | Chatify::makeInFavorite($userId, $favoriteStatus); 292 | 293 | // send the response 294 | return Response::json([ 295 | 'status' => @$favoriteStatus, 296 | ], 200); 297 | } 298 | 299 | /** 300 | * Get favorites list 301 | * 302 | * @param Request $request 303 | * @return JsonResponse|void 304 | */ 305 | public function getFavorites(Request $request) 306 | { 307 | $favoritesList = null; 308 | $favorites = Favorite::where('user_id', Auth::user()->id); 309 | foreach ($favorites->get() as $favorite) { 310 | // get user data 311 | $user = User::where('id', $favorite->favorite_id)->first(); 312 | $favoritesList .= view('Chatify::layouts.favorite', [ 313 | 'user' => $user, 314 | ]); 315 | } 316 | // send the response 317 | return Response::json([ 318 | 'count' => $favorites->count(), 319 | 'favorites' => $favorites->count() > 0 320 | ? $favoritesList 321 | : 0, 322 | ], 200); 323 | } 324 | 325 | /** 326 | * Search in messenger 327 | * 328 | * @param Request $request 329 | * @return JsonResponse|void 330 | */ 331 | public function search(Request $request) 332 | { 333 | $getRecords = null; 334 | $input = trim(filter_var($request['input'])); 335 | $records = User::where('id','!=',Auth::user()->id) 336 | ->where('name', 'LIKE', "%{$input}%") 337 | ->paginate($request->per_page ?? $this->perPage); 338 | foreach ($records->items() as $record) { 339 | $getRecords .= view('Chatify::layouts.listItem', [ 340 | 'get' => 'search_item', 341 | 'user' => Chatify::getUserWithAvatar($record), 342 | ])->render(); 343 | } 344 | if($records->total() < 1){ 345 | $getRecords = '

Nothing to show.

'; 346 | } 347 | // send the response 348 | return Response::json([ 349 | 'records' => $getRecords, 350 | 'total' => $records->total(), 351 | 'last_page' => $records->lastPage() 352 | ], 200); 353 | } 354 | 355 | /** 356 | * Get shared photos 357 | * 358 | * @param Request $request 359 | * @return JsonResponse|void 360 | */ 361 | public function sharedPhotos(Request $request) 362 | { 363 | $shared = Chatify::getSharedPhotos($request['user_id']); 364 | $sharedPhotos = null; 365 | 366 | // shared with its template 367 | for ($i = 0; $i < count($shared); $i++) { 368 | $sharedPhotos .= view('Chatify::layouts.listItem', [ 369 | 'get' => 'sharedPhoto', 370 | 'image' => Chatify::getAttachmentUrl($shared[$i]), 371 | ])->render(); 372 | } 373 | // send the response 374 | return Response::json([ 375 | 'shared' => count($shared) > 0 ? $sharedPhotos : '

Nothing shared yet

', 376 | ], 200); 377 | } 378 | 379 | /** 380 | * Delete conversation 381 | * 382 | * @param Request $request 383 | * @return JsonResponse 384 | */ 385 | public function deleteConversation(Request $request) 386 | { 387 | // delete 388 | $delete = Chatify::deleteConversation($request['id']); 389 | 390 | // send the response 391 | return Response::json([ 392 | 'deleted' => $delete ? 1 : 0, 393 | ], 200); 394 | } 395 | 396 | /** 397 | * Delete message 398 | * 399 | * @param Request $request 400 | * @return JsonResponse 401 | */ 402 | public function deleteMessage(Request $request) 403 | { 404 | // delete 405 | $delete = Chatify::deleteMessage($request['id']); 406 | 407 | // send the response 408 | return Response::json([ 409 | 'deleted' => $delete ? 1 : 0, 410 | ], 200); 411 | } 412 | 413 | public function updateSettings(Request $request) 414 | { 415 | $msg = null; 416 | $error = $success = 0; 417 | 418 | // dark mode 419 | if ($request['dark_mode']) { 420 | $request['dark_mode'] == "dark" 421 | ? User::where('id', Auth::user()->id)->update(['dark_mode' => 1]) // Make Dark 422 | : User::where('id', Auth::user()->id)->update(['dark_mode' => 0]); // Make Light 423 | } 424 | 425 | // If messenger color selected 426 | if ($request['messengerColor']) { 427 | $messenger_color = trim(filter_var($request['messengerColor'])); 428 | User::where('id', Auth::user()->id) 429 | ->update(['messenger_color' => $messenger_color]); 430 | } 431 | // if there is a [file] 432 | if ($request->hasFile('avatar')) { 433 | // allowed extensions 434 | $allowed_images = Chatify::getAllowedImages(); 435 | 436 | $file = $request->file('avatar'); 437 | // check file size 438 | if ($file->getSize() < Chatify::getMaxUploadSize()) { 439 | if (in_array(strtolower($file->extension()), $allowed_images)) { 440 | // delete the older one 441 | if (Auth::user()->avatar != config('chatify.user_avatar.default')) { 442 | $avatar = Auth::user()->avatar; 443 | if (Chatify::storage()->exists($avatar)) { 444 | Chatify::storage()->delete($avatar); 445 | } 446 | } 447 | // upload 448 | $avatar = Str::uuid() . "." . $file->extension(); 449 | $update = User::where('id', Auth::user()->id)->update(['avatar' => $avatar]); 450 | $file->storeAs(config('chatify.user_avatar.folder'), $avatar, config('chatify.storage_disk_name')); 451 | $success = $update ? 1 : 0; 452 | } else { 453 | $msg = "File extension not allowed!"; 454 | $error = 1; 455 | } 456 | } else { 457 | $msg = "File size you are trying to upload is too large!"; 458 | $error = 1; 459 | } 460 | } 461 | 462 | // send the response 463 | return Response::json([ 464 | 'status' => $success ? 1 : 0, 465 | 'error' => $error ? 1 : 0, 466 | 'message' => $error ? $msg : 0, 467 | ], 200); 468 | } 469 | 470 | /** 471 | * Set user's active status 472 | * 473 | * @param Request $request 474 | * @return JsonResponse 475 | */ 476 | public function setActiveStatus(Request $request) 477 | { 478 | $activeStatus = $request['status'] > 0 ? 1 : 0; 479 | $status = User::where('id', Auth::user()->id)->update(['active_status' => $activeStatus]); 480 | return Response::json([ 481 | 'status' => $status, 482 | ], 200); 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /src/MessageCollection.php: -------------------------------------------------------------------------------- 1 | each->markAsRead(); 17 | } 18 | 19 | /** 20 | * Mark all notifications as unread. 21 | * 22 | * @return void 23 | */ 24 | public function markAsUnread() 25 | { 26 | $this->each->markAsUnread(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Models/ChFavorite.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class, 'from_id'); 22 | } 23 | 24 | /** 25 | * Get the user who received the message. 26 | * 27 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 28 | */ 29 | public function to() 30 | { 31 | return $this->belongsTo(User::class, 'to_id'); 32 | } 33 | 34 | /** 35 | * Mark the notification as read. 36 | * 37 | * @return void 38 | */ 39 | public function markAsRead() 40 | { 41 | if ($this->seen !== 1) { 42 | $this->forceFill(['seen' => 1])->save(); 43 | } 44 | } 45 | 46 | /** 47 | * Mark the notification as unread. 48 | * 49 | * @return void 50 | */ 51 | public function markAsUnread() 52 | { 53 | if ($this->seen !== 0) { 54 | $this->forceFill(['seen' => 0])->save(); 55 | } 56 | } 57 | 58 | /** 59 | * Determine if a notification has been read. 60 | * 61 | * @return bool 62 | */ 63 | public function read() 64 | { 65 | return $this->seen !== 0; 66 | } 67 | 68 | /** 69 | * Determine if a notification has not been read. 70 | * 71 | * @return bool 72 | */ 73 | public function unread() 74 | { 75 | return $this->seen === 0; 76 | } 77 | 78 | /** 79 | * Scope a query to only include read notifications. 80 | * 81 | * @param \Illuminate\Database\Eloquent\Builder $query 82 | * @return \Illuminate\Database\Eloquent\Builder 83 | */ 84 | public function scopeRead(Builder $query) 85 | { 86 | return $query->where('seen', 1); 87 | } 88 | 89 | /** 90 | * Scope a query to only include unread notifications. 91 | * 92 | * @param \Illuminate\Database\Eloquent\Builder $query 93 | * @return \Illuminate\Database\Eloquent\Builder 94 | */ 95 | public function scopeUnread(Builder $query) 96 | { 97 | return $query->where('seen', 0); 98 | } 99 | 100 | 101 | /** 102 | * Create a new database notification collection instance. 103 | * 104 | * @param array $models 105 | * @return \Illuminate\Notifications\DatabaseNotificationCollection 106 | */ 107 | public function newCollection(array $models = []) 108 | { 109 | return new MessageCollection($models); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Traits/HasMessage.php: -------------------------------------------------------------------------------- 1 | hasMany(Message::class, 'to_id'); 17 | } 18 | 19 | /** 20 | * Get the entity's read message. 21 | * 22 | * @return \Illuminate\Database\Query\Builder 23 | */ 24 | public function readMessage() 25 | { 26 | return $this->messages()->read(); 27 | } 28 | 29 | /** 30 | * Get the entity's unread message. 31 | * 32 | * @return \Illuminate\Database\Query\Builder 33 | */ 34 | public function unreadMessage() 35 | { 36 | return $this->messages()->unread(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Traits/UUID.php: -------------------------------------------------------------------------------- 1 | {$model->getKeyName()} = (string) Str::uuid(); 14 | }); 15 | } 16 | 17 | public function getIncrementing () 18 | { 19 | return false; 20 | } 21 | 22 | public function getKeyType () 23 | { 24 | return 'string'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/css/dark.mode.css: -------------------------------------------------------------------------------- 1 | /*app scroll*/ 2 | .app-scroll::-webkit-scrollbar-thumb, 3 | .app-scroll-thin::-webkit-scrollbar-thumb { 4 | background: var(--dark-scrollbar-thumb-color); 5 | } 6 | .app-scroll-thin::-webkit-scrollbar { 7 | background: var(--dark-secondary-bg-color); 8 | } 9 | .app-scroll::-webkit-scrollbar:hover, 10 | .app-scroll-thin::-webkit-scrollbar:hover { 11 | background: var(--dark-secondary-bg-color); 12 | } 13 | .messenger { 14 | background: var(--dark-primary-bg-color); 15 | } 16 | .messenger-search[type="text"] { 17 | background: var(--dark-secondary-bg-color); 18 | color: #fff; 19 | } 20 | .messenger-search[type="text"]::placeholder { 21 | color: #fff; 22 | } 23 | .messenger-listView { 24 | background: var(--dark-primary-bg-color); 25 | border: 1px solid var(--dark-border-color); 26 | } 27 | .messenger-listView-tabs { 28 | border-bottom: 1px solid var(--dark-border-color); 29 | } 30 | .messenger-listView-tabs a:hover, 31 | .messenger-listView-tabs a:focus { 32 | background-color: var(--dark-secondary-bg-color); 33 | } 34 | .messenger-favorites div.avatar { 35 | border: 2px solid var(--dark-primary-bg-color); 36 | } 37 | .messenger-list-item:hover { 38 | background: var(--dark-secondary-bg-color); 39 | } 40 | .messenger-messagingView { 41 | border-top: 1px solid var(--dark-secondary-bg-color); 42 | border-bottom: 1px solid var(--dark-secondary-bg-color); 43 | background: var(--dark-messagingView-bg-color); 44 | } 45 | .m-header-messaging { 46 | background: var(--dark-primary-bg-color); 47 | } 48 | .messenger-infoView { 49 | background: var(--dark-primary-bg-color); 50 | border: 1px solid var(--dark-border-color); 51 | } 52 | .messenger-infoView > p { 53 | color: #fff; 54 | } 55 | .divider { 56 | border-top: 1px solid var(--dark-border-color); 57 | } 58 | .messenger-sendCard { 59 | background: var(--dark-primary-bg-color); 60 | border-top: 1px solid var(--dark-border-color); 61 | } 62 | .attachment-preview > p { 63 | color: #fff; 64 | } 65 | .m-send { 66 | color: #fff; 67 | } 68 | .m-send::placeholder { 69 | color: #fff; 70 | } 71 | .message-card .message { 72 | background: var(--dark-message-card-color); 73 | color: #fff; 74 | } 75 | .m-li-divider { 76 | border-bottom: 1px solid var(--dark-border-color); 77 | } 78 | .m-header a, 79 | .m-header a:hover, 80 | .m-header a:focus { 81 | text-decoration: none; 82 | color: #fff; 83 | } 84 | .messenger-list-item td p { 85 | color: #fff; 86 | } 87 | .activeStatus { 88 | border: 2px solid var(--dark-border-color); 89 | } 90 | .messenger-list-item:hover .activeStatus { 91 | border-color: var(--dark-secondary-bg-color); 92 | } 93 | .messenger-favorites > div p { 94 | color: #ffffff; 95 | } 96 | .avatar { 97 | background-color: var(--dark-secondary-bg-color); 98 | border-color: var(--dark-border-color); 99 | } 100 | .messenger-sendCard svg { 101 | color: var(--dark-send-input-icons-color); 102 | } 103 | .messenger-title { 104 | color: #dbdbdb; 105 | } 106 | .messenger-title > span { 107 | background-color: var(--dark-primary-bg-color); 108 | } 109 | .messenger-title::before { 110 | background-color: var(--dark-border-color); 111 | } 112 | .message-hint span { 113 | background: var(--dark-message-hint-bg-color); 114 | color: var(--dark-message-hint-color); 115 | } 116 | .messenger-infoView > nav > p { 117 | color: #fff; 118 | } 119 | /* 120 | *********************************************** 121 | * Placeholder loading 122 | *********************************************** 123 | */ 124 | .loadingPlaceholder-body div, 125 | .loadingPlaceholder-header tr td div { 126 | background: var(--dark-secondary-bg-color); 127 | background-image: -webkit-linear-gradient( 128 | left, 129 | var(--dark-secondary-bg-color) 0%, 130 | var(--dark-secondary-bg-color) 20%, 131 | var(--dark-secondary-bg-color) 40%, 132 | var(--dark-secondary-bg-color) 100% 133 | ); 134 | } 135 | 136 | /* 137 | *********************************************** 138 | * App Modal 139 | *********************************************** 140 | */ 141 | 142 | .app-modal-card { 143 | background: var(--dark-modal-bg-color); 144 | } 145 | .app-modal-header { 146 | color: #fff; 147 | } 148 | .app-modal-body { 149 | color: #fff; 150 | } 151 | 152 | .messages .message-time { 153 | color: #fff; 154 | } 155 | 156 | .message-card .actions .delete-btn { 157 | color: #fff; 158 | } 159 | -------------------------------------------------------------------------------- /src/assets/css/light.mode.css: -------------------------------------------------------------------------------- 1 | /*app scroll*/ 2 | .app-scroll::-webkit-scrollbar-thumb, 3 | .app-scroll-thin::-webkit-scrollbar-thumb { 4 | background: var(--scrollbar-thumb-color); 5 | } 6 | .app-scroll-thin::-webkit-scrollbar { 7 | background: var(--secondary-bg-color); 8 | } 9 | .app-scroll::-webkit-scrollbar:hover, 10 | .app-scroll-thin::-webkit-scrollbar:hover { 11 | background: var(--secondary-bg-color); 12 | } 13 | 14 | .messenger { 15 | background: var(--primary-bg-color); 16 | } 17 | .messenger-search[type="text"] { 18 | background: var(--secondary-bg-color); 19 | color: #333; 20 | } 21 | .messenger-listView { 22 | background: var(--primary-bg-color); 23 | border: 1px solid var(--border-color); 24 | } 25 | .messenger-listView-tabs { 26 | border-bottom: 1px solid var(--border-color); 27 | } 28 | .messenger-listView-tabs a:hover, 29 | .messenger-listView-tabs a:focus { 30 | background-color: var(--secondary-bg-color); 31 | } 32 | .messenger-favorites div.avatar { 33 | border: 2px solid var(--primary-bg-color); 34 | } 35 | 36 | .messenger-list-item:hover { 37 | background: var(--secondary-bg-color); 38 | } 39 | .messenger-messagingView { 40 | border-top: 1px solid var(--secondary-bg-color); 41 | border-bottom: 1px solid var(--secondary-bg-color); 42 | background: var(--messagingView-bg-color); 43 | } 44 | .m-header-messaging { 45 | background: var(--primary-bg-color); 46 | } 47 | .messenger-infoView { 48 | background: var(--primary-bg-color); 49 | border: 1px solid var(--border-color); 50 | } 51 | .messenger-infoView > p { 52 | color: #000; 53 | } 54 | .divider { 55 | border-top: 1px solid var(--border-color); 56 | } 57 | .messenger-sendCard { 58 | background: var(--primary-bg-color); 59 | border-top: 1px solid var(--border-color); 60 | } 61 | .attachment-preview > p { 62 | color: #333; 63 | } 64 | .m-send { 65 | color: #333; 66 | } 67 | .message-card .message { 68 | background: var(--message-card-color); 69 | color: #656b75; 70 | box-shadow: 0px 6px 11px rgba(18, 67, 105, 0.03); 71 | } 72 | .m-li-divider { 73 | border-bottom: 1px solid var(--border-color); 74 | } 75 | .m-header a, 76 | .m-header a:hover, 77 | .m-header a:focus { 78 | text-decoration: none; 79 | color: #202020; 80 | } 81 | .messenger-list-item td p { 82 | color: #3c3c3c; 83 | } 84 | .messenger-list-item td span { 85 | color: #929292; 86 | } 87 | .activeStatus { 88 | border: 2px solid var(--primary-bg-color); 89 | } 90 | .messenger-list-item:hover .activeStatus { 91 | border-color: var(--secondary-bg-color); 92 | } 93 | .messenger-favorites > div p { 94 | color: #4a4a4a; 95 | } 96 | 97 | .avatar { 98 | background-color: var(--secondary-bg-color); 99 | border-color: var(--border-color); 100 | } 101 | .messenger-sendCard svg { 102 | color: var(--send-input-icons-color); 103 | } 104 | .messenger-title { 105 | color: #797979; 106 | } 107 | .messenger-title > span { 108 | background-color: var(--primary-bg-color); 109 | } 110 | .messenger-title::before { 111 | background-color: var(--border-color); 112 | } 113 | .message-hint span { 114 | background: var(--message-hint-bg-color); 115 | color: var(--message-hint-color); 116 | } 117 | /* 118 | *********************************************** 119 | * Placeholder loading 120 | *********************************************** 121 | */ 122 | .loadingPlaceholder-body div, 123 | .loadingPlaceholder-header tr td div { 124 | background: var(--secondary-bg-color); 125 | background-image: -webkit-linear-gradient( 126 | left, 127 | var(--secondary-bg-color) 0%, 128 | var(--secondary-bg-color) 20%, 129 | var(--secondary-bg-color) 40%, 130 | var(--secondary-bg-color) 100% 131 | ); 132 | } 133 | .messenger-infoView > nav > p { 134 | color: #333; 135 | } 136 | /* 137 | *********************************************** 138 | * App Modal 139 | *********************************************** 140 | */ 141 | 142 | .app-modal-card { 143 | background: var(--modal-bg-color); 144 | } 145 | .app-modal-header { 146 | color: #000; 147 | } 148 | .app-modal-body { 149 | color: #000; 150 | } 151 | 152 | /* 153 | ***************************************** 154 | * Responsive Design 155 | ***************************************** 156 | */ 157 | @media (max-width: 1060px) { 158 | .messenger-infoView { 159 | box-shadow: 0px 0px 20px rgba(18, 67, 105, 0.06); 160 | } 161 | } 162 | @media (max-width: 980px) { 163 | .messenger-listView { 164 | box-shadow: 0px 0px 20px rgba(18, 67, 105, 0.06); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/assets/css/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 6 | Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; 7 | } 8 | 9 | :root { 10 | /* 11 | * -------------------------------------------------- 12 | * NOTE: `--primary-color` variable set in 13 | * `headLinks.blade.php` view file. 14 | * -------------------------------------------------- 15 | */ 16 | 17 | /* General variables */ 18 | --icon-size: 20px; 19 | --headers-padding: 1rem; 20 | --listView-header-height: 110px; 21 | 22 | /* Light theme variables */ 23 | --primary-bg-color: #fff; 24 | --secondary-bg-color: #f7f7f7; 25 | --border-color: #eee; 26 | --messagingView-bg-color: #f6f7f9; 27 | --scrollbar-thumb-color: #cfcfcf; 28 | --modal-bg-color: #fff; 29 | --send-input-icons-color: #4b4b4b; 30 | --message-hint-bg-color: #ededed; 31 | --message-hint-color: #4b4b4b; 32 | --message-card-color: #fff; 33 | 34 | /* Dark theme variables */ 35 | --dark-primary-bg-color: #121212; 36 | --dark-secondary-bg-color: #202020; 37 | --dark-border-color: #202020; 38 | --dark-messagingView-bg-color: #1b1b1b; 39 | --dark-scrollbar-thumb-color: #212121; 40 | --dark-modal-bg-color: #1a1a1a; 41 | --dark-send-input-icons-color: #c8c8c8; 42 | --dark-message-hint-bg-color: #292929; 43 | --dark-message-hint-color: #ffffff; 44 | --dark-message-card-color: #292929; 45 | } 46 | 47 | /* NProgress background */ 48 | #nprogress .bar { 49 | background: var(--primary-color) !important; 50 | } 51 | #nprogress .peg { 52 | box-shadow: 0 0 10px var(--primary-color), 0 0 5px var(--primary-color) !important; 53 | } 54 | #nprogress .spinner-icon { 55 | border-top-color: var(--primary-color) !important; 56 | border-left-color: var(--primary-color) !important; 57 | } 58 | 59 | /*internet connection*/ 60 | .internet-connection { 61 | display: none; 62 | background: rgba(0, 0, 0, 0.76); 63 | position: absolute; 64 | bottom: calc( 65 | -100% + (var(--headers-padding) + var(--headers-padding)) - 8px 66 | ); /* 8px = 4px padding-top + 4px padding-bottom */ 67 | left: 0; 68 | right: 0; 69 | text-align: center; 70 | padding: 4px; 71 | color: #fff; 72 | z-index: 1; 73 | } 74 | .internet-connection span { 75 | display: none; 76 | } 77 | 78 | /*green background RGBA*/ 79 | .successBG-rgba { 80 | background: rgba(54, 180, 36, 0.76) !important; 81 | } 82 | 83 | /* app scroll*/ 84 | .app-scroll::-webkit-scrollbar { 85 | width: 5px; 86 | height: 5px; 87 | border-radius: 4px; 88 | background: transparent; 89 | transition: all 0.3s ease; 90 | } 91 | .app-scroll-hidden::-webkit-scrollbar { 92 | width: 0px; 93 | height: 0px; 94 | } 95 | .app-scroll::-webkit-scrollbar-thumb, 96 | .app-scroll-hidden::-webkit-scrollbar-thumb { 97 | border-radius: 0px; 98 | } 99 | .messenger-headTitle { 100 | margin: 0rem 0.7rem; 101 | } 102 | .messenger { 103 | display: inline-flex; 104 | width: 100%; 105 | height: 100%; 106 | font-family: sans-serif; 107 | } 108 | .messenger-listView { 109 | display: flex; 110 | flex-direction: column; 111 | gap: 5px; 112 | position: relative; 113 | top: 0px; 114 | left: 0px; 115 | right: 0px; 116 | z-index: 1; 117 | background: transparent; 118 | width: 45%; 119 | min-width: 200px; 120 | overflow: auto; 121 | } 122 | .messenger-listView .m-header { 123 | height: var(--listView-header-height); 124 | } 125 | .messenger-listView .m-header > nav { 126 | padding: var(--headers-padding); 127 | } 128 | .messenger-messagingView { 129 | display: flex; 130 | flex-direction: column; 131 | gap: 5px; 132 | overflow: hidden; 133 | width: 100%; 134 | } 135 | .messenger-messagingView .m-header { 136 | padding: var(--headers-padding); 137 | } 138 | .messenger-messagingView .m-body { 139 | position: relative; 140 | padding-top: 15px; 141 | overflow-x: hidden; 142 | overflow-y: auto; 143 | height: 100%; 144 | } 145 | .m-header { 146 | font-weight: 600; 147 | background: transparent; 148 | } 149 | .m-header-right { 150 | display: flex; 151 | align-items: center; 152 | gap: 1rem; 153 | float: right; 154 | } 155 | .m-header-messaging { 156 | position: relative; 157 | background: #fff; 158 | box-shadow: 0px 5px 6px rgba(0, 0, 0, 0.06); 159 | } 160 | .m-header svg { 161 | color: var(--primary-color); 162 | font-size: var(--icon-size); 163 | transition: transform 0.12s; 164 | } 165 | .m-header svg:active { 166 | transform: scale(0.9); 167 | } 168 | .messenger-search[type="text"] { 169 | margin: 0px 10px; 170 | width: calc(100% - 20px); 171 | border: none; 172 | padding: 8px 10px; 173 | border-radius: 6px; 174 | outline: none; 175 | } 176 | .messenger-listView-tabs { 177 | display: inline-flex; 178 | width: 100%; 179 | margin-top: 10px; 180 | background-color: transparent; 181 | box-shadow: 0px 5px 6px rgba(0, 0, 0, 0.06); 182 | } 183 | .messenger-listView-tabs a { 184 | display: flex; 185 | align-items: center; 186 | justify-content: center; 187 | gap: 1rem; 188 | width: 100%; 189 | text-align: center; 190 | padding: 10px; 191 | text-decoration: none; 192 | background-color: transparent; 193 | transition: background 0.3s; 194 | } 195 | .messenger-listView-tabs a:hover, 196 | .messenger-listView-tabs a:focus { 197 | text-decoration: none; 198 | } 199 | .messenger-listView-tabs a, 200 | .messenger-listView-tabs a:hover, 201 | .messenger-listView-tabs a:focus { 202 | color: var(--primary-color); 203 | } 204 | .active-tab { 205 | border-bottom: 2px solid var(--primary-color); 206 | } 207 | .messenger-tab { 208 | overflow: auto; 209 | height: calc(100vh - var(--listView-header-height) - 2px); 210 | display: none; 211 | position: relative; 212 | } 213 | .add-to-favorite { 214 | display: none; 215 | } 216 | .add-to-favorite svg { 217 | color: rgba(180, 180, 180, 0.52) !important; 218 | } 219 | .favorite-added svg { 220 | color: #ffc107 !important; 221 | } 222 | .favorite svg { 223 | color: #ffc107 !important; 224 | } 225 | .show { 226 | display: block; 227 | } 228 | .hide { 229 | display: none; 230 | } 231 | .messenger-list-item { 232 | margin: 0; 233 | width: 100%; 234 | cursor: pointer; 235 | transition: background 0.1s; 236 | } 237 | .m-list-active span, 238 | .m-list-active p { 239 | color: #fff !important; 240 | } 241 | 242 | .m-list-active, 243 | .m-list-active:hover, 244 | .m-list-active:focus { 245 | background: var(--primary-color) !important; 246 | } 247 | .m-list-active b { 248 | background: #fff !important; 249 | color: var(--primary-color) !important; 250 | } 251 | .m-list-active .activeStatus { 252 | border-color: var(--primary-color) !important; 253 | } 254 | .messenger-list-item td { 255 | padding: 10px; 256 | } 257 | .messenger-list-item tr > td:first-child { 258 | padding-right: 0; 259 | width: 55px; 260 | } 261 | .messenger-list-item td p { 262 | margin-bottom: 4px; 263 | font-size: 14px; 264 | } 265 | .messenger-list-item td p span { 266 | float: right; 267 | } 268 | .messenger-list-item td span { 269 | color: #cacaca; 270 | font-weight: 400; 271 | font-size: 12px; 272 | } 273 | .messenger-list-item td b { 274 | float: right; 275 | color: #fff; 276 | background: var(--primary-color); 277 | padding: 0px 4px; 278 | border-radius: 20px; 279 | font-size: 13px; 280 | width: auto; 281 | height: auto; 282 | text-align: center; 283 | } 284 | .avatar { 285 | text-align: center; 286 | border-radius: 100%; 287 | border: 1px solid; 288 | overflow: hidden; 289 | background-image: url(""); 290 | background-repeat: no-repeat; 291 | background-size: cover; 292 | background-position: center center; 293 | } 294 | .av-l { 295 | width: 100px; 296 | height: 100px; 297 | } 298 | .av-m { 299 | width: 45px; 300 | height: 45px; 301 | } 302 | .av-s { 303 | width: 32px !important; 304 | height: 32px !important; 305 | } 306 | .saved-messages.avatar { 307 | background-color: transparent; 308 | text-align: center; 309 | display: flex; 310 | flex-direction: column; 311 | align-items: center; 312 | justify-content: center; 313 | } 314 | .saved-messages.avatar > svg { 315 | font-size: 22px; 316 | color: var(--primary-color); 317 | } 318 | .messenger-list-item.m-list-active .saved-messages.avatar > svg { 319 | color: #fff; 320 | } 321 | .messenger-list-item.m-list-active .saved-messages.avatar { 322 | border-color: #ffffff81; 323 | } 324 | .messenger-favorites { 325 | padding: 10px; 326 | overflow: auto; 327 | white-space: nowrap; 328 | } 329 | .messenger-favorites > div { 330 | display: inline-block; 331 | text-align: center; 332 | transition: transform 0.3s; 333 | cursor: pointer; 334 | } 335 | .messenger-favorites > div p { 336 | font-size: 12px; 337 | margin: 8px 0px; 338 | margin-bottom: 0px; 339 | } 340 | .messenger-favorites div.avatar { 341 | border: 2px solid #fff; 342 | margin: 0px 4px; 343 | box-shadow: 0px 0px 0px 2px var(--primary-color); 344 | } 345 | .messenger-favorites > div:active { 346 | transform: scale(0.9); 347 | } 348 | .messenger-title { 349 | position: relative; 350 | margin: 0; 351 | padding: 10px !important; 352 | text-transform: capitalize; 353 | font-size: 12px; 354 | text-align: center; 355 | z-index: 1; 356 | } 357 | .messenger-title > span { 358 | position: relative; 359 | padding: 0px 10px; 360 | z-index: 1; 361 | } 362 | .messenger-title::before { 363 | content: ""; 364 | display: block; 365 | width: 100%; 366 | height: 1px; 367 | position: absolute; 368 | bottom: 50%; 369 | left: 0; 370 | right: 0; 371 | z-index: 0; 372 | } 373 | .messenger-infoView { 374 | display: block; 375 | overflow: auto; 376 | width: 40%; 377 | min-width: 200px; 378 | } 379 | .messenger-infoView nav { 380 | display: flex; 381 | align-items: center; 382 | justify-content: space-between; 383 | padding: var(--headers-padding); 384 | } 385 | .messenger-infoView nav a { 386 | color: var(--primary-color); 387 | text-decoration: none; 388 | font-size: var(--icon-size); 389 | } 390 | .messenger-infoView > div { 391 | margin: auto; 392 | margin-top: 8%; 393 | text-align: center; 394 | } 395 | .messenger-infoView > p { 396 | text-align: center; 397 | margin: auto; 398 | margin-top: 15px; 399 | font-size: 18px; 400 | font-weight: 600; 401 | } 402 | .messenger-infoView-btns a { 403 | display: block; 404 | text-decoration: none !important; 405 | padding: 5px 10px; 406 | margin: 0% 10%; 407 | border-radius: 3px; 408 | font-size: 14px; 409 | transition: background 0.3s; 410 | } 411 | .messenger-infoView-btns a.default { 412 | color: var(--primary-color); 413 | } 414 | .messenger-infoView-btns a.default:hover { 415 | background: #f0f6ff; 416 | } 417 | .messenger-infoView-btns a.danger { 418 | color: #ff5555; 419 | } 420 | .messenger-infoView-btns a.danger:hover { 421 | background: rgba(255, 85, 85, 0.11); 422 | } 423 | .shared-photo { 424 | border-radius: 3px; 425 | background: #f7f7f7; 426 | height: 120px; 427 | overflow: hidden; 428 | display: inline-block; 429 | margin: 0px 1px; 430 | width: calc(50% - 12px); 431 | background-position: center center; 432 | background-size: cover; 433 | background-repeat: no-repeat; 434 | cursor: pointer; 435 | } 436 | .shared-photo img { 437 | width: auto; 438 | height: 100%; 439 | } 440 | .messenger-infoView-shared { 441 | display: none; 442 | } 443 | .messenger-infoView-shared .messenger-title { 444 | padding-bottom: 10px; 445 | } 446 | .messenger-infoView-btns .delete-conversation { 447 | display: none; 448 | } 449 | .message-card { 450 | display: flex; 451 | flex-direction: row; 452 | gap: 0.5rem; 453 | align-items: center; 454 | width: 100%; 455 | margin: 2px 15px; 456 | width: calc(100% - 30px); /* 30px = 15px padding left + right */ 457 | justify-content: flex-start; 458 | } 459 | .message-card .message-card-content { 460 | display: flex; 461 | flex-direction: column; 462 | gap: 4px; 463 | max-width: 60%; 464 | } 465 | .message-card.mc-sender .message-card-content { 466 | align-items: end; 467 | } 468 | .message-card .image-wrapper .image-file { 469 | position: relative; 470 | } 471 | .message-card .image-wrapper .image-file > div { 472 | display: none; 473 | position: absolute; 474 | bottom: 0; 475 | right: 0; 476 | left: 0; 477 | background: linear-gradient( 478 | 0deg, 479 | rgba(0, 0, 0, 1) 0%, 480 | rgba(0, 0, 0, 0.5) 100% 481 | ); 482 | padding: 0.5rem; 483 | font-size: 11px; 484 | color: #fff; 485 | } 486 | .message-card-content:hover .image-wrapper .image-file > div { 487 | display: block; 488 | } 489 | .message-card div { 490 | margin-top: 0px; 491 | } 492 | .message-card .message { 493 | margin: 0; 494 | padding: 6px 15px; 495 | padding-bottom: 5px; 496 | width: fit-content; 497 | width: -webkit-fit-content; 498 | border-radius: 20px; 499 | word-break: break-word; 500 | display: table-cell; 501 | } 502 | .message-card .message-time { 503 | display: inline-block; 504 | font-size: 11px; 505 | } 506 | .message-card .message .message-time:before { 507 | content: ""; 508 | background: transparent; 509 | width: 4px; 510 | height: 4px; 511 | display: inline-block; 512 | } 513 | .message-card.mc-sender { 514 | justify-content: flex-end; 515 | } 516 | .message-card.mc-sender .message { 517 | direction: ltr; 518 | color: #fff !important; 519 | background: var(--primary-color) !important; 520 | } 521 | .message-card.mc-sender .message .message-time { 522 | color: rgba(255, 255, 255, 0.67); 523 | } 524 | 525 | .mc-error .message { 526 | background: rgba(255, 0, 0, 0.27) !important; 527 | color: #ff0000 !important; 528 | } 529 | .mc-error .message .message-time { 530 | color: #ff0000 !important; 531 | } 532 | .messenger-sendCard .send-button svg { 533 | color: var(--primary-color); 534 | } 535 | .listView-x, 536 | .show-listView { 537 | display: none; 538 | } 539 | .messenger-sendCard { 540 | display: none; 541 | margin: 10px; 542 | margin-bottom: 1rem; 543 | border-radius: 8px; 544 | padding-left: 8px; 545 | padding-right: 8px; 546 | } 547 | .messenger-sendCard form { 548 | width: 100%; 549 | display: flex; 550 | align-items: center; 551 | justify-content: center; 552 | margin: 0; 553 | } 554 | .messenger-sendCard input[type="file"] { 555 | display: none; 556 | } 557 | .messenger-sendCard button, 558 | .messenger-sendCard button:active, 559 | .messenger-sendCard button:focus { 560 | border: none; 561 | outline: none; 562 | background: none; 563 | padding: 0; 564 | margin: 0; 565 | } 566 | .messenger-sendCard label { 567 | margin: 0; 568 | } 569 | .messenger-sendCard svg { 570 | margin: 9px 10px; 571 | color: #bdcbd6; 572 | cursor: pointer; 573 | font-size: 21px; 574 | transition: transform 0.15s; 575 | } 576 | 577 | .messenger-sendCard svg:active { 578 | transform: scale(0.9); 579 | } 580 | .m-send { 581 | font-size: 14px; 582 | width: 100%; 583 | border: none; 584 | padding: 10px; 585 | outline: none; 586 | resize: none; 587 | background: transparent; 588 | font-family: sans-serif; 589 | height: 44px; 590 | max-height: 200px; 591 | } 592 | .attachment-preview { 593 | position: relative; 594 | padding: 10px; 595 | } 596 | 597 | .attachment-preview > p { 598 | margin: 0; 599 | font-size: 12px; 600 | padding: 0px; 601 | padding-top: 10px; 602 | } 603 | .attachment-preview > p > svg { 604 | font-size: 16px; 605 | margin: 0; 606 | margin-bottom: -1px; 607 | color: #737373; 608 | } 609 | .attachment-preview svg:active { 610 | transform: none; 611 | } 612 | .message-card .image-file, 613 | .attachment-preview .image-file { 614 | cursor: pointer; 615 | width: 140px; 616 | height: 70px; 617 | border-radius: 6px; 618 | width: 260px; 619 | height: 170px; 620 | overflow: hidden; 621 | background-color: #f7f7f7; 622 | background-size: cover; 623 | background-repeat: no-repeat; 624 | background-position: center center; 625 | } 626 | .attachment-preview > svg:first-child { 627 | position: absolute; 628 | background: rgba(0, 0, 0, 0.33); 629 | width: 20px; 630 | height: 20px; 631 | padding: 3px; 632 | border-radius: 100%; 633 | font-size: 16px; 634 | margin: 0; 635 | top: 10px; 636 | color: #fff; 637 | } 638 | #message-form > button { 639 | height: 40px; 640 | } 641 | .file-download { 642 | font-size: 12px; 643 | display: block; 644 | color: #fff; 645 | text-decoration: none; 646 | font-weight: 600; 647 | border: 1px solid rgba(0, 0, 0, 0.08); 648 | background: rgba(0, 0, 0, 0.03); 649 | padding: 2px 8px; 650 | margin-top: 10px; 651 | border-radius: 20px; 652 | transition: transform 0.3s, background 0.3s; 653 | } 654 | .file-download:hover, 655 | .file-download:focus { 656 | color: #fff; 657 | text-decoration: none; 658 | background: rgba(0, 0, 0, 0.08); 659 | } 660 | .file-download:active { 661 | transform: scale(0.95); 662 | } 663 | .typing-indicator { 664 | display: none; 665 | } 666 | .messages { 667 | padding: 5px 0px; 668 | display: flex; 669 | flex-direction: column; 670 | gap: 4px; 671 | } 672 | .message-hint { 673 | margin: 0; 674 | text-align: center; 675 | } 676 | .center-el { 677 | position: absolute; 678 | left: 50%; 679 | top: 50%; 680 | transform: translate(-50%, -50%); 681 | } 682 | .message-hint span { 683 | padding: 3px 10px; 684 | border-radius: 20px; 685 | display: inline-block; 686 | } 687 | .upload-avatar-details { 688 | font-size: 14px; 689 | color: #949ba5; 690 | display: none; 691 | } 692 | .upload-avatar-preview { 693 | position: relative; 694 | border: 1px solid #e0e0e0; 695 | margin: 20px auto; 696 | } 697 | .upload-avatar-loading { 698 | position: absolute; 699 | top: calc(50% - 21px); 700 | margin: 0; 701 | left: calc(50% - 20px); 702 | } 703 | .divider { 704 | margin: 15px; 705 | } 706 | .update-messengerColor { 707 | margin: 1rem 0rem; 708 | } 709 | .update-messengerColor .color-btn { 710 | width: 30px; 711 | height: 30px; 712 | border-radius: 20px; 713 | display: inline-block; 714 | cursor: pointer; 715 | } 716 | .m-color-active { 717 | border: 3px solid rgba(255, 255, 255, 0.5); 718 | } 719 | .update-messengerColor .color-btn { 720 | transition: transform 0.15s, border 0.15s; 721 | } 722 | .update-messengerColor .color-btn:active { 723 | transform: scale(0.9); 724 | } 725 | .dark-mode-switch { 726 | margin: 0px 5px; 727 | cursor: pointer; 728 | color: var(--primary-color); 729 | } 730 | .activeStatus { 731 | width: 12px; 732 | height: 12px; 733 | background: #4caf50; 734 | border-radius: 20px; 735 | position: absolute; 736 | bottom: 12%; 737 | right: 6%; 738 | transition: border 0.1s; 739 | } 740 | .lastMessageIndicator { 741 | color: var(--primary-color) !important; 742 | } 743 | 744 | /* 745 | *********************************************** 746 | * App Buttons 747 | *********************************************** 748 | */ 749 | .app-btn { 750 | cursor: pointer; 751 | border: none; 752 | padding: 3px 15px; 753 | border-radius: 20px; 754 | margin: 1px; 755 | font-size: 14px; 756 | display: inline-block; 757 | outline: none; 758 | text-decoration: none; 759 | transition: all 0.3s; 760 | color: rgb(33, 128, 243); 761 | } 762 | .app-btn:hover, 763 | .app-btn:focus { 764 | color: rgb(33, 128, 243); 765 | outline: none; 766 | text-decoration: none; 767 | } 768 | .app-btn:active { 769 | transform: scale(0.9); 770 | } 771 | .a-btn-light { 772 | background: #f1f1f1; 773 | color: #333; 774 | } 775 | .a-btn-light:hover, 776 | .a-btn-light:focus { 777 | color: #333; 778 | background: #e4e4e4; 779 | } 780 | .a-btn-primary { 781 | background: #0976d6; 782 | color: #fff; 783 | } 784 | .a-btn-primary:hover, 785 | .a-btn-primary:focus { 786 | background: #0085ef; 787 | color: #fff; 788 | } 789 | .a-btn-warning { 790 | background: #ffc107; 791 | color: #fff; 792 | } 793 | .a-btn-warning:hover, 794 | .a-btn-warning:focus { 795 | background: #ffa726; 796 | color: #fff; 797 | } 798 | .a-btn-success { 799 | background: #1e8a53 !important; 800 | color: #fff; 801 | } 802 | .a-btn-success:hover, 803 | .a-btn-success:focus { 804 | background: #2ecc71 !important; 805 | color: #fff; 806 | } 807 | .a-btn-danger { 808 | background: #ea1909 !important; 809 | color: #fff; 810 | } 811 | .a-btn-danger:hover, 812 | .a-btn-danger:focus { 813 | color: #fff; 814 | background: #b70d00 !important; 815 | } 816 | .btn-disabled { 817 | opacity: 0.5; 818 | } 819 | /* 820 | *********************************************** 821 | * App Modal 822 | *********************************************** 823 | */ 824 | .app-modal { 825 | display: none; 826 | position: fixed; 827 | top: 0; 828 | bottom: 0; 829 | right: 0; 830 | left: 0; 831 | background: rgba(0, 0, 0, 0.53); 832 | z-index: 50; 833 | } 834 | .app-modal-container { 835 | position: absolute; 836 | left: 50%; 837 | top: 50%; 838 | transform: translate(-50%, -50%); 839 | } 840 | .app-modal-card { 841 | width: auto; 842 | max-width: 400px; 843 | margin: auto; 844 | border-radius: 5px; 845 | text-align: center; 846 | box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.27); 847 | transform: scale(0); 848 | } 849 | .app-modal-form { 850 | padding: 20px 40px; 851 | } 852 | .app-modal-header { 853 | font-weight: 500; 854 | } 855 | .app-modal-footer { 856 | margin-top: 10px; 857 | } 858 | .app-show-modal { 859 | transform: scale(1); 860 | animation: show_modal 0.15s; 861 | } 862 | /* modal animation */ 863 | @keyframes show_modal { 864 | from { 865 | transform: scale(0); 866 | } 867 | to { 868 | transform: scale(1); 869 | } 870 | } 871 | 872 | /* 873 | *********************************************** 874 | * Placeholder loading 875 | *********************************************** 876 | */ 877 | .loadingPlaceholder-wrapper { 878 | position: relative; 879 | } 880 | 881 | .loadingPlaceholder-body div, 882 | .loadingPlaceholder-header tr td div { 883 | background-repeat: no-repeat; 884 | background-size: 800px 104px; 885 | height: 104px; 886 | position: relative; 887 | } 888 | 889 | .loadingPlaceholder-body div { 890 | position: absolute; 891 | right: 0px; 892 | left: 0px; 893 | top: 0px; 894 | } 895 | 896 | div.loadingPlaceholder-avatar { 897 | height: 45px !important; 898 | width: 45px; 899 | margin: 10px; 900 | border-radius: 60px; 901 | } 902 | div.loadingPlaceholder-name { 903 | height: 15px !important; 904 | margin-bottom: 10px; 905 | width: 150px; 906 | border-radius: 2px; 907 | } 908 | 909 | div.loadingPlaceholder-date { 910 | height: 10px !important; 911 | width: 106px; 912 | border-radius: 2px; 913 | } 914 | /* 915 | *********************************************** 916 | * Image modal box 917 | *********************************************** 918 | */ 919 | .imageModal { 920 | display: none; 921 | position: fixed; 922 | z-index: 50; 923 | padding-top: 100px; 924 | left: 0; 925 | top: 0; 926 | width: 100%; 927 | height: 100%; 928 | overflow: auto; 929 | background-color: rgb(0, 0, 0); 930 | background-color: rgba(0, 0, 0, 0.9); 931 | } 932 | .imageModal-content { 933 | margin: auto; 934 | display: block; 935 | height: calc(100vh - 150px); 936 | } 937 | .imageModal-content { 938 | -webkit-animation-name: zoom; 939 | -webkit-animation-duration: 0.15s; 940 | animation-name: zoom; 941 | animation-duration: 0.15s; 942 | } 943 | 944 | @-webkit-keyframes zoom { 945 | from { 946 | -webkit-transform: scale(0); 947 | } 948 | to { 949 | -webkit-transform: scale(1); 950 | } 951 | } 952 | @keyframes zoom { 953 | from { 954 | transform: scale(0); 955 | } 956 | to { 957 | transform: scale(1); 958 | } 959 | } 960 | 961 | .imageModal-close { 962 | position: absolute; 963 | top: 15px; 964 | right: 35px; 965 | color: #f1f1f1; 966 | font-size: 40px; 967 | font-weight: bold; 968 | transition: 0.3s; 969 | } 970 | 971 | .imageModal-close:hover, 972 | .imageModal-close:focus { 973 | color: #bbb; 974 | text-decoration: none; 975 | cursor: pointer; 976 | } 977 | 978 | /* 979 | *********************************************** 980 | * Typing (jumping) dots animation and style 981 | *********************************************** 982 | */ 983 | .dot { 984 | width: 8px; 985 | height: 8px; 986 | background: #bcc1c6; 987 | display: inline-block; 988 | border-radius: 50%; 989 | right: 0px; 990 | bottom: 0px; 991 | position: relative; 992 | animation: jump 1s infinite; 993 | } 994 | 995 | .typing-dots .dot-1 { 996 | -webkit-animation-delay: 100ms; 997 | animation-delay: 100ms; 998 | } 999 | 1000 | .typing-dots .dot-2 { 1001 | -webkit-animation-delay: 200ms; 1002 | animation-delay: 200ms; 1003 | } 1004 | 1005 | .typing-dots .dot-3 { 1006 | -webkit-animation-delay: 300ms; 1007 | animation-delay: 300ms; 1008 | } 1009 | 1010 | @keyframes jump { 1011 | 0% { 1012 | bottom: 0px; 1013 | } 1014 | 20% { 1015 | bottom: 5px; 1016 | } 1017 | 40% { 1018 | bottom: 0px; 1019 | } 1020 | } 1021 | /* 1022 | ***************************************** 1023 | * Responsive Design 1024 | ***************************************** 1025 | */ 1026 | @media (max-width: 1060px) { 1027 | .messenger-infoView { 1028 | position: fixed; 1029 | right: 0; 1030 | top: 0; 1031 | bottom: 0; 1032 | max-width: 334px; 1033 | } 1034 | } 1035 | @media (max-width: 980px) { 1036 | .messenger-listView.conversation-active { 1037 | display: none; 1038 | } 1039 | .messenger-listView { 1040 | position: fixed; 1041 | left: 0; 1042 | top: 0; 1043 | bottom: 0; 1044 | max-width: 334px; 1045 | } 1046 | .listView-x { 1047 | display: block; 1048 | } 1049 | .show-listView { 1050 | display: inline-block; 1051 | } 1052 | } 1053 | @media (max-width: 680px) { 1054 | .messenger-messagingView { 1055 | position: fixed; 1056 | top: 0; 1057 | left: 0; 1058 | height: 100%; 1059 | } 1060 | .messenger-infoView { 1061 | display: none; 1062 | width: 100%; 1063 | max-width: unset; 1064 | } 1065 | .messenger-listView { 1066 | width: 100%; 1067 | max-width: unset; 1068 | } 1069 | .listView-x { 1070 | display: none; 1071 | } 1072 | .app-modal-container { 1073 | transform: unset; 1074 | } 1075 | .app-modal-card { 1076 | max-width: unset; 1077 | position: fixed; 1078 | left: 0; 1079 | right: 0; 1080 | top: 0; 1081 | bottom: 0; 1082 | width: 100%; 1083 | height: 100%; 1084 | border-radius: 0px; 1085 | } 1086 | } 1087 | @media (min-width: 680px) { 1088 | .messenger-listView { 1089 | display: unset; 1090 | } 1091 | } 1092 | @media only screen and (max-width: 700px) { 1093 | .imageModal-content { 1094 | width: 100%; 1095 | } 1096 | } 1097 | 1098 | @media (max-width: 576px) { 1099 | .user-name { 1100 | max-width: 150px; 1101 | white-space: nowrap; 1102 | overflow: hidden !important; 1103 | text-overflow: ellipsis; 1104 | } 1105 | .chatify-md-block { 1106 | display: block; 1107 | } 1108 | } 1109 | 1110 | .chatify-d-flex { 1111 | display: flex !important; 1112 | } 1113 | 1114 | .chatify-d-none { 1115 | display: none !important; 1116 | } 1117 | 1118 | .chatify-d-hidden { 1119 | visibility: hidden !important; 1120 | } 1121 | 1122 | .chatify-justify-content-between { 1123 | justify-content: space-between !important; 1124 | } 1125 | 1126 | .chatify-align-items-center { 1127 | align-items: center !important; 1128 | } 1129 | 1130 | .chat-message-wrapper { 1131 | display: flex; 1132 | flex-direction: column; 1133 | align-items: end; 1134 | unicode-bidi: bidi-override; 1135 | direction: ltr; 1136 | } 1137 | 1138 | .pb-3 { 1139 | padding-bottom: 0.75rem; /* 12px */ 1140 | } 1141 | 1142 | .mb-2 { 1143 | margin-bottom: 0.5rem; /* 8px */ 1144 | } 1145 | 1146 | .messenger [type="text"]:focus { 1147 | outline: 1px solid var(--primary-color); 1148 | border-color: var(--primary-color) !important; 1149 | border-color: var(--primary-color); 1150 | box-shadow: 0 0 2px var(--primary-color); 1151 | } 1152 | 1153 | .messenger textarea:focus { 1154 | outline: none; 1155 | border: none; 1156 | box-shadow: none; 1157 | } 1158 | .message-card .actions { 1159 | opacity: 0.6; 1160 | } 1161 | .message-card .actions .delete-btn { 1162 | display: none; 1163 | cursor: pointer; 1164 | color: #333333; 1165 | } 1166 | 1167 | .message-card:hover .actions .delete-btn { 1168 | display: block; 1169 | } 1170 | 1171 | /* 1172 | ***************************************** 1173 | * Emoji Button scroll-bars 1174 | ***************************************** 1175 | */ 1176 | .emoji-picker__emojis::-webkit-scrollbar { 1177 | width: 5px; 1178 | height: 5px; 1179 | border-radius: 4px; 1180 | background: transparent; 1181 | transition: all 0.3s ease; 1182 | } 1183 | .emoji-picker__emojis::-webkit-scrollbar-thumb { 1184 | border-radius: 4px; 1185 | background: transparent; 1186 | } 1187 | -------------------------------------------------------------------------------- /src/assets/imgs/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/munafio/chatify/11cefa98ee9563c166f68605c97a03b5e46679b1/src/assets/imgs/avatar.png -------------------------------------------------------------------------------- /src/assets/js/autosize.js: -------------------------------------------------------------------------------- 1 | /* 2 | **************************************************************************** 3 | * Text Area auto resize 4 | **************************************************************************** 5 | */ 6 | (function (global, factory) { 7 | if (typeof define === "function" && define.amd) { 8 | define(['module', 'exports'], factory); 9 | } else if (typeof exports !== "undefined") { 10 | factory(module, exports); 11 | } else { 12 | var mod = { 13 | exports: {} 14 | }; 15 | factory(mod, mod.exports); 16 | global.autosize = mod.exports; 17 | } 18 | })(this, function (module, exports) { 19 | 'use strict'; 20 | 21 | var map = typeof Map === "function" ? new Map() : function () { 22 | var keys = []; 23 | var values = []; 24 | 25 | return { 26 | has: function has(key) { 27 | return keys.indexOf(key) > -1; 28 | }, 29 | get: function get(key) { 30 | return values[keys.indexOf(key)]; 31 | }, 32 | set: function set(key, value) { 33 | if (keys.indexOf(key) === -1) { 34 | keys.push(key); 35 | values.push(value); 36 | } 37 | }, 38 | delete: function _delete(key) { 39 | var index = keys.indexOf(key); 40 | if (index > -1) { 41 | keys.splice(index, 1); 42 | values.splice(index, 1); 43 | } 44 | } 45 | }; 46 | }(); 47 | 48 | var createEvent = function createEvent(name) { 49 | return new Event(name, { bubbles: true }); 50 | }; 51 | try { 52 | new Event('test'); 53 | } catch (e) { 54 | // IE does not support `new Event()` 55 | createEvent = function createEvent(name) { 56 | var evt = document.createEvent('Event'); 57 | evt.initEvent(name, true, false); 58 | return evt; 59 | }; 60 | } 61 | 62 | function assign(ta) { 63 | if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return; 64 | 65 | var heightOffset = null; 66 | var clientWidth = null; 67 | var cachedHeight = null; 68 | 69 | function init() { 70 | var style = window.getComputedStyle(ta, null); 71 | 72 | if (style.resize === 'vertical') { 73 | ta.style.resize = 'none'; 74 | } else if (style.resize === 'both') { 75 | ta.style.resize = 'horizontal'; 76 | } 77 | 78 | if (style.boxSizing === 'content-box') { 79 | heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)); 80 | } else { 81 | heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth); 82 | } 83 | // Fix when a textarea is not on document body and heightOffset is Not a Number 84 | if (isNaN(heightOffset)) { 85 | heightOffset = 0; 86 | } 87 | 88 | update(); 89 | } 90 | 91 | function changeOverflow(value) { 92 | { 93 | // Chrome/Safari-specific fix: 94 | // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space 95 | // made available by removing the scrollbar. The following forces the necessary text reflow. 96 | var width = ta.style.width; 97 | ta.style.width = '0px'; 98 | // Force reflow: 99 | /* jshint ignore:start */ 100 | ta.offsetWidth; 101 | /* jshint ignore:end */ 102 | ta.style.width = width; 103 | } 104 | 105 | ta.style.overflowY = value; 106 | } 107 | 108 | function getParentOverflows(el) { 109 | var arr = []; 110 | 111 | while (el && el.parentNode && el.parentNode instanceof Element) { 112 | if (el.parentNode.scrollTop) { 113 | arr.push({ 114 | node: el.parentNode, 115 | scrollTop: el.parentNode.scrollTop 116 | }); 117 | } 118 | el = el.parentNode; 119 | } 120 | 121 | return arr; 122 | } 123 | 124 | function resize() { 125 | if (ta.scrollHeight === 0) { 126 | // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM. 127 | return; 128 | } 129 | 130 | var overflows = getParentOverflows(ta); 131 | var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240) 132 | 133 | ta.style.height = ''; 134 | ta.style.height = ta.scrollHeight + heightOffset + 'px'; 135 | 136 | // used to check if an update is actually necessary on window.resize 137 | clientWidth = ta.clientWidth; 138 | 139 | // prevents scroll-position jumping 140 | overflows.forEach(function (el) { 141 | el.node.scrollTop = el.scrollTop; 142 | }); 143 | 144 | if (docTop) { 145 | document.documentElement.scrollTop = docTop; 146 | } 147 | } 148 | 149 | function update() { 150 | resize(); 151 | 152 | var styleHeight = Math.round(parseFloat(ta.style.height)); 153 | var computed = window.getComputedStyle(ta, null); 154 | 155 | // Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box 156 | var actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(computed.height)) : ta.offsetHeight; 157 | 158 | // The actual height not matching the style height (set via the resize method) indicates that 159 | // the max-height has been exceeded, in which case the overflow should be allowed. 160 | if (actualHeight < styleHeight) { 161 | if (computed.overflowY === 'hidden') { 162 | changeOverflow('scroll'); 163 | resize(); 164 | actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight; 165 | } 166 | } else { 167 | // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands. 168 | if (computed.overflowY !== 'hidden') { 169 | changeOverflow('hidden'); 170 | resize(); 171 | actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight; 172 | } 173 | } 174 | 175 | if (cachedHeight !== actualHeight) { 176 | cachedHeight = actualHeight; 177 | var evt = createEvent('autosize:resized'); 178 | try { 179 | ta.dispatchEvent(evt); 180 | } catch (err) { 181 | // Firefox will throw an error on dispatchEvent for a detached element 182 | // https://bugzilla.mozilla.org/show_bug.cgi?id=889376 183 | } 184 | } 185 | } 186 | 187 | var pageResize = function pageResize() { 188 | if (ta.clientWidth !== clientWidth) { 189 | update(); 190 | } 191 | }; 192 | 193 | var destroy = function (style) { 194 | window.removeEventListener('resize', pageResize, false); 195 | ta.removeEventListener('input', update, false); 196 | ta.removeEventListener('keyup', update, false); 197 | ta.removeEventListener('autosize:destroy', destroy, false); 198 | ta.removeEventListener('autosize:update', update, false); 199 | 200 | Object.keys(style).forEach(function (key) { 201 | ta.style[key] = style[key]; 202 | }); 203 | 204 | map.delete(ta); 205 | }.bind(ta, { 206 | height: ta.style.height, 207 | resize: ta.style.resize, 208 | overflowY: ta.style.overflowY, 209 | overflowX: ta.style.overflowX, 210 | wordWrap: ta.style.wordWrap 211 | }); 212 | 213 | ta.addEventListener('autosize:destroy', destroy, false); 214 | 215 | // IE9 does not fire onpropertychange or oninput for deletions, 216 | // so binding to onkeyup to catch most of those events. 217 | // There is no way that I know of to detect something like 'cut' in IE9. 218 | if ('onpropertychange' in ta && 'oninput' in ta) { 219 | ta.addEventListener('keyup', update, false); 220 | } 221 | 222 | window.addEventListener('resize', pageResize, false); 223 | ta.addEventListener('input', update, false); 224 | ta.addEventListener('autosize:update', update, false); 225 | ta.style.overflowX = 'hidden'; 226 | ta.style.wordWrap = 'break-word'; 227 | 228 | map.set(ta, { 229 | destroy: destroy, 230 | update: update 231 | }); 232 | 233 | init(); 234 | } 235 | 236 | function destroy(ta) { 237 | var methods = map.get(ta); 238 | if (methods) { 239 | methods.destroy(); 240 | } 241 | } 242 | 243 | function update(ta) { 244 | var methods = map.get(ta); 245 | if (methods) { 246 | methods.update(); 247 | } 248 | } 249 | 250 | var autosize = null; 251 | 252 | // Do nothing in Node.js environment and IE8 (or lower) 253 | if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') { 254 | autosize = function autosize(el) { 255 | return el; 256 | }; 257 | autosize.destroy = function (el) { 258 | return el; 259 | }; 260 | autosize.update = function (el) { 261 | return el; 262 | }; 263 | } else { 264 | autosize = function autosize(el, options) { 265 | if (el) { 266 | Array.prototype.forEach.call(el.length ? el : [el], function (x) { 267 | return assign(x, options); 268 | }); 269 | } 270 | return el; 271 | }; 272 | autosize.destroy = function (el) { 273 | if (el) { 274 | Array.prototype.forEach.call(el.length ? el : [el], destroy); 275 | } 276 | return el; 277 | }; 278 | autosize.update = function (el) { 279 | if (el) { 280 | Array.prototype.forEach.call(el.length ? el : [el], update); 281 | } 282 | return el; 283 | }; 284 | } 285 | 286 | exports.default = autosize; 287 | module.exports = exports['default']; 288 | }); -------------------------------------------------------------------------------- /src/assets/js/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * changes date string to time ago string. 3 | * @param dateString - The date string to convert to a time ago string. 4 | * @returns A string that tells the user how long ago the date was. 5 | */ 6 | function dateStringToTimeAgo(dateString) { 7 | const now = new Date(); 8 | const date = new Date(dateString); 9 | const seconds = Math.floor((now - date) / 1000); 10 | const minutes = Math.floor(seconds / 60); 11 | const hours = Math.floor(minutes / 60); 12 | const days = Math.floor(hours / 24); 13 | const weeks = Math.floor(days / 7); 14 | if (seconds < 60) { 15 | return "just now"; 16 | } else if (minutes < 60) { 17 | return `${minutes}m ago`; 18 | } else if (hours < 24) { 19 | return `${hours}h ago`; 20 | } else if (days < 7) { 21 | return `${days}d ago`; 22 | } else { 23 | return `${weeks}w ago`; 24 | } 25 | } 26 | /** 27 | * It returns a function that, when invoked, will wait for a specified amount of time before executing 28 | * the original function. 29 | * @param callback - The function to be executed after the delay. 30 | * @param delay - The amount of time to wait before calling the callback. 31 | * @returns A function that will call the callback function after a delay. 32 | */ 33 | function debounce(callback, delay) { 34 | let timerId; 35 | return function (...args) { 36 | clearTimeout(timerId); 37 | timerId = setTimeout(() => { 38 | callback.apply(this, args); 39 | }, delay); 40 | }; 41 | } 42 | 43 | /** 44 | * Sanitizes a potentially unsafe string to prevent XSS and other injection issues. 45 | * This function escapes special characters such as `<`, `>`, `&`, and `"` by converting 46 | * them into their corresponding HTML entities. This ensures that any inserted HTML or JavaScript 47 | * code is treated as plain text and not executed by the browser. 48 | * 49 | * @param {string} inputValue - The input string that may contain unsafe characters. 50 | * @returns {string} - A sanitized output string with special characters converted to HTML entities. 51 | * 52 | * @throws {Error} - Throws an error if the input is not a string. 53 | * 54 | * Example usage: 55 | * const unsafeString = ''; 56 | * const safeString = sanitizeInput(unsafeString); 57 | * console.log(safeString); // Output: "<script>alert("XSS!")</script>" 58 | * 59 | * Test cases: 60 | * const testInputs = [ 61 | * "", 62 | * "", 63 | * "", 64 | * ">", 65 | * "", 66 | * "", 67 | * "
", 68 | * "Click me", 69 | * "
Hover me
", 70 | * "", 71 | * "", 72 | * "
", 73 | * "
", 74 | * "" 75 | * ]; 76 | */ 77 | 78 | 79 | 80 | function sanitizeInput(inputValue) { 81 | if (typeof inputValue !== 'string') { 82 | return ''; 83 | } 84 | 85 | if (!/^[a-zA-Z0-9\s\-_]+$/.test(inputValue)) { 86 | return inputValue 87 | .replace(/&/g, '&') 88 | .replace(//g, '>') 90 | .replace(/"/g, '"') 91 | .replace(/'/g, '''); 92 | } 93 | return inputValue 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/assets/sounds/new-message-sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/munafio/chatify/11cefa98ee9563c166f68605c97a03b5e46679b1/src/assets/sounds/new-message-sound.mp3 -------------------------------------------------------------------------------- /src/config/chatify.php: -------------------------------------------------------------------------------- 1 | env('CHATIFY_NAME', 'Chatify Messenger'), 10 | 11 | /* 12 | |------------------------------------- 13 | | The disk on which to store added 14 | | files and derived images by default. 15 | |------------------------------------- 16 | */ 17 | 'storage_disk_name' => env('CHATIFY_STORAGE_DISK', 'public'), 18 | 19 | /* 20 | |------------------------------------- 21 | | Routes configurations 22 | |------------------------------------- 23 | */ 24 | 'routes' => [ 25 | 'custom' => env('CHATIFY_CUSTOM_ROUTES', false), 26 | 'prefix' => env('CHATIFY_ROUTES_PREFIX', 'chatify'), 27 | 'middleware' => env('CHATIFY_ROUTES_MIDDLEWARE', ['web','auth']), 28 | 'namespace' => env('CHATIFY_ROUTES_NAMESPACE', 'Chatify\Http\Controllers'), 29 | ], 30 | 'api_routes' => [ 31 | 'prefix' => env('CHATIFY_API_ROUTES_PREFIX', 'chatify/api'), 32 | 'middleware' => env('CHATIFY_API_ROUTES_MIDDLEWARE', ['api']), 33 | 'namespace' => env('CHATIFY_API_ROUTES_NAMESPACE', 'Chatify\Http\Controllers\Api'), 34 | ], 35 | 36 | /* 37 | |------------------------------------- 38 | | Pusher API credentials 39 | |------------------------------------- 40 | */ 41 | 'pusher' => [ 42 | 'debug' => env('APP_DEBUG', false), 43 | 'key' => env('PUSHER_APP_KEY'), 44 | 'secret' => env('PUSHER_APP_SECRET'), 45 | 'app_id' => env('PUSHER_APP_ID'), 46 | 'options' => [ 47 | 'cluster' => env('PUSHER_APP_CLUSTER', 'mt1'), 48 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', 49 | 'port' => env('PUSHER_PORT', 443), 50 | 'scheme' => env('PUSHER_SCHEME', 'https'), 51 | 'encrypted' => true, 52 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', 53 | ], 54 | ], 55 | 56 | /* 57 | |------------------------------------- 58 | | User Avatar 59 | |------------------------------------- 60 | */ 61 | 'user_avatar' => [ 62 | 'folder' => 'users-avatar', 63 | 'default' => 'avatar.png', 64 | ], 65 | 66 | /* 67 | |------------------------------------- 68 | | Gravatar 69 | | 70 | | imageset property options: 71 | | [ 404 | mp | identicon (default) | monsterid | wavatar ] 72 | |------------------------------------- 73 | */ 74 | 'gravatar' => [ 75 | 'enabled' => true, 76 | 'image_size' => 200, 77 | 'imageset' => 'identicon' 78 | ], 79 | 80 | /* 81 | |------------------------------------- 82 | | Attachments 83 | |------------------------------------- 84 | */ 85 | 'attachments' => [ 86 | 'folder' => 'attachments', 87 | 'download_route_name' => 'attachments.download', 88 | 'allowed_images' => (array) ['png','jpg','jpeg','gif'], 89 | 'allowed_files' => (array) ['zip','rar','txt'], 90 | 'max_upload_size' => env('CHATIFY_MAX_FILE_SIZE', 150), // MB 91 | ], 92 | 93 | /* 94 | |------------------------------------- 95 | | Messenger's colors 96 | |------------------------------------- 97 | */ 98 | 'colors' => (array) [ 99 | '#2180f3', 100 | '#2196F3', 101 | '#00BCD4', 102 | '#3F51B5', 103 | '#673AB7', 104 | '#4CAF50', 105 | '#FFC107', 106 | '#FF9800', 107 | '#ff2522', 108 | '#9C27B0', 109 | ], 110 | /* 111 | |------------------------------------- 112 | | Sounds 113 | | You can enable/disable the sounds and 114 | | change sound's name/path placed at 115 | | `public/` directory of your app. 116 | | 117 | |------------------------------------- 118 | */ 119 | 'sounds' => [ 120 | 'enabled' => true, 121 | 'public_path' => 'sounds/chatify', 122 | 'new_message' => 'new-message-sound.mp3', 123 | ] 124 | ]; 125 | -------------------------------------------------------------------------------- /src/database/migrations/2022_01_10_99999_add_active_status_to_users.php: -------------------------------------------------------------------------------- 1 | boolean('active_status')->default(0); 20 | } 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::table('users', function (Blueprint $table) { 32 | $table->dropColumn('active_status'); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/database/migrations/2022_01_10_99999_add_avatar_to_users.php: -------------------------------------------------------------------------------- 1 | string('avatar')->default(config('chatify.user_avatar.default')); 20 | } 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::table('users', function (Blueprint $table) { 32 | $table->dropColumn('avatar'); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/database/migrations/2022_01_10_99999_add_dark_mode_to_users.php: -------------------------------------------------------------------------------- 1 | boolean('dark_mode')->default(0); 20 | } 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::table('users', function (Blueprint $table) { 32 | $table->dropColumn('dark_mode'); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/database/migrations/2022_01_10_99999_add_messenger_color_to_users.php: -------------------------------------------------------------------------------- 1 | string('messenger_color')->nullable(); 19 | } 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::table('users', function (Blueprint $table) { 31 | $table->dropColumn('messenger_color'); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/database/migrations/2022_01_10_99999_create_chatify_favorites_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 18 | $table->bigInteger('user_id'); 19 | $table->bigInteger('favorite_id'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('ch_favorites'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/database/migrations/2022_01_10_99999_create_chatify_messages_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 18 | $table->bigInteger('from_id'); 19 | $table->bigInteger('to_id'); 20 | $table->string('body',5000)->nullable(); 21 | $table->string('attachment')->nullable(); 22 | $table->boolean('seen')->default(false); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('ch_messages'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/routes/api.php: -------------------------------------------------------------------------------- 1 | name('api.pusher.auth'); 9 | 10 | /** 11 | * Fetch info for specific id [user/group] 12 | */ 13 | Route::post('/idInfo', 'MessagesController@idFetchData')->name('api.idInfo'); 14 | 15 | /** 16 | * Send message route 17 | */ 18 | Route::post('/sendMessage', 'MessagesController@send')->name('api.send.message'); 19 | 20 | /** 21 | * Fetch messages 22 | */ 23 | Route::post('/fetchMessages', 'MessagesController@fetch')->name('api.fetch.messages'); 24 | 25 | /** 26 | * Download attachments route to create a downloadable links 27 | */ 28 | Route::get('/download/{fileName}', 'MessagesController@download')->name('api.'.config('chatify.attachments.download_route_name')); 29 | 30 | /** 31 | * Make messages as seen 32 | */ 33 | Route::post('/makeSeen', 'MessagesController@seen')->name('api.messages.seen'); 34 | 35 | /** 36 | * Get contacts 37 | */ 38 | Route::get('/getContacts', 'MessagesController@getContacts')->name('api.contacts.get'); 39 | 40 | /** 41 | * Star in favorite list 42 | */ 43 | Route::post('/star', 'MessagesController@favorite')->name('api.star'); 44 | 45 | /** 46 | * get favorites list 47 | */ 48 | Route::post('/favorites', 'MessagesController@getFavorites')->name('api.favorites'); 49 | 50 | /** 51 | * Search in messenger 52 | */ 53 | Route::get('/search', 'MessagesController@search')->name('api.search'); 54 | 55 | /** 56 | * Get shared photos 57 | */ 58 | Route::post('/shared', 'MessagesController@sharedPhotos')->name('api.shared'); 59 | 60 | /** 61 | * Delete Conversation 62 | */ 63 | Route::post('/deleteConversation', 'MessagesController@deleteConversation')->name('api.conversation.delete'); 64 | 65 | /** 66 | * Delete Conversation 67 | */ 68 | Route::post('/updateSettings', 'MessagesController@updateSettings')->name('api.avatar.update'); 69 | 70 | /** 71 | * Set active status 72 | */ 73 | Route::post('/setActiveStatus', 'MessagesController@setActiveStatus')->name('api.activeStatus.set'); 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/routes/web.php: -------------------------------------------------------------------------------- 1 | name(config('chatify.routes.prefix')); 17 | 18 | /** 19 | * Fetch info for specific id [user/group] 20 | */ 21 | Route::post('/idInfo', 'MessagesController@idFetchData'); 22 | 23 | /** 24 | * Send message route 25 | */ 26 | Route::post('/sendMessage', 'MessagesController@send')->name('send.message'); 27 | 28 | /** 29 | * Fetch messages 30 | */ 31 | Route::post('/fetchMessages', 'MessagesController@fetch')->name('fetch.messages'); 32 | 33 | /** 34 | * Download attachments route to create a downloadable links 35 | */ 36 | Route::get('/download/{fileName}', 'MessagesController@download')->name(config('chatify.attachments.download_route_name')); 37 | 38 | /** 39 | * Authentication for pusher private channels 40 | */ 41 | Route::post('/chat/auth', 'MessagesController@pusherAuth')->name('pusher.auth'); 42 | 43 | /** 44 | * Make messages as seen 45 | */ 46 | Route::post('/makeSeen', 'MessagesController@seen')->name('messages.seen'); 47 | 48 | /** 49 | * Get contacts 50 | */ 51 | Route::get('/getContacts', 'MessagesController@getContacts')->name('contacts.get'); 52 | 53 | /** 54 | * Update contact item data 55 | */ 56 | Route::post('/updateContacts', 'MessagesController@updateContactItem')->name('contacts.update'); 57 | 58 | 59 | /** 60 | * Star in favorite list 61 | */ 62 | Route::post('/star', 'MessagesController@favorite')->name('star'); 63 | 64 | /** 65 | * get favorites list 66 | */ 67 | Route::post('/favorites', 'MessagesController@getFavorites')->name('favorites'); 68 | 69 | /** 70 | * Search in messenger 71 | */ 72 | Route::get('/search', 'MessagesController@search')->name('search'); 73 | 74 | /** 75 | * Get shared photos 76 | */ 77 | Route::post('/shared', 'MessagesController@sharedPhotos')->name('shared'); 78 | 79 | /** 80 | * Delete Conversation 81 | */ 82 | Route::post('/deleteConversation', 'MessagesController@deleteConversation')->name('conversation.delete'); 83 | 84 | /** 85 | * Delete Message 86 | */ 87 | Route::post('/deleteMessage', 'MessagesController@deleteMessage')->name('message.delete'); 88 | 89 | /** 90 | * Update setting 91 | */ 92 | Route::post('/updateSettings', 'MessagesController@updateSettings')->name('avatar.update'); 93 | 94 | /** 95 | * Set active status 96 | */ 97 | Route::post('/setActiveStatus', 'MessagesController@setActiveStatus')->name('activeStatus.set'); 98 | 99 | 100 | 101 | 102 | 103 | 104 | /* 105 | * [Group] view by id 106 | */ 107 | Route::get('/group/{id}', 'MessagesController@index')->name('group'); 108 | 109 | /* 110 | * user view by id. 111 | * Note : If you added routes after the [User] which is the below one, 112 | * it will considered as user id. 113 | * 114 | * e.g. - The commented routes below : 115 | */ 116 | // Route::get('/route', function(){ return 'Munaf'; }); // works as a route 117 | Route::get('/{id}', 'MessagesController@index')->name('user'); 118 | // Route::get('/route', function(){ return 'Munaf'; }); // works as a user id 119 | -------------------------------------------------------------------------------- /src/views/layouts/favorite.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @if($user) 3 |
5 |
6 |

{{ strlen($user->name) > 5 ? substr($user->name,0,6).'..' : $user->name }}

7 | @endif 8 |
9 | -------------------------------------------------------------------------------- /src/views/layouts/footerLinks.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/views/layouts/headLinks.blade.php: -------------------------------------------------------------------------------- 1 | {{ config('chatify.name') }} 2 | 3 | {{-- Meta tags --}} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{-- scripts --}} 12 | 14 | 15 | 16 | 17 | 18 | 19 | {{-- styles --}} 20 | 21 | 22 | 23 | 24 | 25 | {{-- Setting messenger primary color to css --}} 26 | 31 | -------------------------------------------------------------------------------- /src/views/layouts/info.blade.php: -------------------------------------------------------------------------------- 1 | {{-- user info and avatar --}} 2 |
3 |

{{ config('chatify.name') }}

4 | 7 | {{-- shared photos --}} 8 |
9 |

Shared Photos

10 |
11 |
12 | -------------------------------------------------------------------------------- /src/views/layouts/listItem.blade.php: -------------------------------------------------------------------------------- 1 | {{-- -------------------- Saved Messages -------------------- --}} 2 | @if($get == 'saved') 3 | 4 | 5 | {{-- Avatar side --}} 6 | 11 | {{-- center side --}} 12 | 16 | 17 |
7 |
8 | 9 |
10 |
13 |

Saved Messages You

14 | Save messages secretly 15 |
18 | @endif 19 | 20 | {{-- -------------------- Contact list -------------------- --}} 21 | @if($get == 'users' && !!$lastMessage) 22 | body, 'UTF-8', 'UTF-8'); 24 | $lastMessageBody = strlen($lastMessageBody) > 30 ? mb_substr($lastMessageBody, 0, 30, 'UTF-8').'..' : $lastMessageBody; 25 | ?> 26 | 27 | 28 | {{-- Avatar side --}} 29 | 37 | {{-- center side --}} 38 | 61 | 62 |
30 | @if($user->active_status) 31 | 32 | @endif 33 |
35 |
36 |
39 |

40 | {{ strlen($user->name) > 12 ? trim(substr($user->name,0,12)).'..' : $user->name }} 41 | {{ $lastMessage->timeAgo }}

42 | 43 | {{-- Last Message user indicator --}} 44 | {!! 45 | $lastMessage->from_id == Auth::user()->id 46 | ? 'You :' 47 | : '' 48 | !!} 49 | {{-- Last message body --}} 50 | @if($lastMessage->attachment == null) 51 | {!! 52 | $lastMessageBody 53 | !!} 54 | @else 55 | Attachment 56 | @endif 57 | 58 | {{-- New messages counter --}} 59 | {!! $unseenCounter > 0 ? "".$unseenCounter."" : '' !!} 60 |
63 | @endif 64 | 65 | {{-- -------------------- Search Item -------------------- --}} 66 | @if($get == 'search_item') 67 | 68 | 69 | {{-- Avatar side --}} 70 | 75 | {{-- center side --}} 76 | 80 | 81 | 82 |
71 |
73 |
74 |
77 |

78 | {{ strlen($user->name) > 12 ? trim(substr($user->name,0,12)).'..' : $user->name }} 79 |

83 | @endif 84 | 85 | {{-- -------------------- Shared photos Item -------------------- --}} 86 | @if($get == 'sharedPhoto') 87 |
88 | @endif 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/views/layouts/messageCard.blade.php: -------------------------------------------------------------------------------- 1 | 4 | ".($isSender ? "" : '' )." $timeAgo 5 | "; 6 | ?> 7 | 8 |
9 | {{-- Delete Message Button --}} 10 | @if ($isSender) 11 |
12 | 13 |
14 | @endif 15 | {{-- Card --}} 16 |
17 | @if (@$attachment->type != 'image' || $message) 18 |
19 | {!! ($message == null && $attachment != null && @$attachment->type != 'file') ? $attachment->title : nl2br($message) !!} 20 | {!! $timeAndSeen !!} 21 | {{-- If attachment is a file --}} 22 | @if(@$attachment->type == 'file') 23 | 24 | {{$attachment->title}} 25 | @endif 26 |
27 | @endif 28 | @if(@$attachment->type == 'image') 29 |
30 |
31 |
{{ $attachment->title }}
32 |
33 |
34 | {!! $timeAndSeen !!} 35 |
36 |
37 | @endif 38 |
39 |
40 | -------------------------------------------------------------------------------- /src/views/layouts/modals.blade.php: -------------------------------------------------------------------------------- 1 | {{-- ---------------------- Image modal box ---------------------- --}} 2 |
3 | × 4 | 5 |
6 | 7 | {{-- ---------------------- Delete Modal ---------------------- --}} 8 |
9 |
10 |
11 |
Are you sure you want to delete this?
12 |
You can not undo this action
13 | 17 |
18 |
19 |
20 | {{-- ---------------------- Alert Modal ---------------------- --}} 21 |
22 |
23 |
24 |
25 |
26 | 29 |
30 |
31 |
32 | {{-- ---------------------- Settings Modal ---------------------- --}} 33 |
34 |
35 |
36 |
37 |
38 | @csrf 39 | {{--
Update your profile settings
--}} 40 |
41 | {{-- Udate profile avatar --}} 42 |
45 |

46 | 50 | {{-- Dark/Light Mode --}} 51 |

52 |

Dark Mode

55 | {{-- change messenger color --}} 56 |

57 | {{--

Change {{ config('chatify.name') }} Color

--}} 58 |
59 | @foreach (config('chatify.colors') as $color) 60 | 61 | @if (($loop->index + 1) % 5 == 0) 62 |
63 | @endif 64 | @endforeach 65 |
66 |
67 | 71 |
72 |
73 |
74 |
75 |
76 | -------------------------------------------------------------------------------- /src/views/layouts/sendForm.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | @csrf 4 | 5 | 6 | 7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /src/views/pages/app.blade.php: -------------------------------------------------------------------------------- 1 | @include('Chatify::layouts.headLinks') 2 |
3 | {{-- ----------------------Users/Groups lists side---------------------- --}} 4 |
5 | {{-- Header and search bar --}} 6 | 23 | {{-- tabs and lists --}} 24 |
25 | {{-- Lists [Users/Group] --}} 26 | {{-- ---------------- [ User Tab ] ---------------- --}} 27 |
28 | {{-- Favorites --}} 29 |
30 |

Favorites

31 |
32 |
33 | {{-- Saved Messages --}} 34 |

Your Space

35 | {!! view('Chatify::layouts.listItem', ['get' => 'saved']) !!} 36 | {{-- Contact --}} 37 |

All Messages

38 |
39 |
40 | {{-- ---------------- [ Search Tab ] ---------------- --}} 41 |
42 | {{-- items --}} 43 |

Search

44 |
45 |

Type to search..

46 |
47 |
48 |
49 |
50 | 51 | {{-- ----------------------Messaging side---------------------- --}} 52 |
53 | {{-- header title [conversation name] amd buttons --}} 54 | 77 | 78 | {{-- Messaging area --}} 79 |
80 |
81 |

Please select a chat to start messaging

82 |
83 | {{-- Typing indicator --}} 84 |
85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 |
93 |
94 |
95 | 96 |
97 | {{-- Send Message Form --}} 98 | @include('Chatify::layouts.sendForm') 99 |
100 | {{-- ---------------------- Info side ---------------------- --}} 101 |
102 | {{-- nav actions --}} 103 | 107 | {!! view('Chatify::layouts.info')->render() !!} 108 |
109 |
110 | 111 | @include('Chatify::layouts.modals') 112 | @include('Chatify::layouts.footerLinks') 113 | --------------------------------------------------------------------------------