├── .gitignore
├── LICENSE
├── README.md
├── TODO.md
├── composer.json
└── src
├── Facades
└── Messenger.php
├── Http
└── Controllers
│ └── MessageController.php
├── LaravelMessengerServiceProvider.php
├── Messenger.php
├── Models
├── Conversation.php
└── Message.php
├── assets
├── css
│ └── messenger.css
├── js
│ └── messenger-chat.js
└── sounds
│ └── tweet.mp3
├── config
└── messenger.php
├── database
└── migrations
│ ├── 2017_10_24_140229_create_conversaions_table.php
│ └── 2017_10_24_140250_create_messages_table.php
├── routes
└── messenger.php
└── views
├── messenger.blade.php
└── partials
├── messages.blade.php
└── threads.blade.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .tags
2 | /vendor/
3 | NOTES.PHP
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Bakly Systems
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 | # Laravel Chat Messenger
2 | A Laravel JQuery chat that is just like Facebook chat
3 |
4 | ## Installation
5 |
6 | Laravel Messenger supports Laravel 5.3 and higher.
7 |
8 | ### Package
9 |
10 | Require via composer
11 |
12 | ```bash
13 | $ composer require baklysystems/laravel-chat-messenger
14 | ```
15 |
16 | In `config/app.php` file
17 |
18 | ```php
19 | 'providers' => [
20 | ...
21 | BaklySystems\LaravelMessenger\LaravelMessengerServiceProvider::class,
22 | ...
23 | ];
24 |
25 | 'aliases' => [
26 | ...
27 | 'Messenger' => BaklySystems\LaravelMessenger\Facades\Messenger::class,
28 | ...
29 | ];
30 | ```
31 |
32 | ### Laravel Messenger Files
33 |
34 | Then, run `php artisan vendor:publish` to publish the config file, MessageController and assets and routes.
35 | ### Laravel Messenger Styles and Scripts
36 |
37 | Make sure to add `@yield('css-styles')` in your app/master head section and `@yield('js-scripts')` to your app/master scripts section, or edit section naming in `view/vendor/messenger/messenger.blade.php`
38 |
39 | JQuery is required for the messenger script.
40 |
41 | Make sure to add `include('messenger.php');` to your web.php
42 |
43 | ### Laravel Messenger Pusher
44 |
45 | Add your pusher keys in `config/messenger.php` file.
46 |
47 | And voila, you can start conversation with any user by linking to `your-domain.com/messenger/t/{userId}`.
48 |
49 | ## Customization
50 |
51 | ### Migrations
52 |
53 | To publish and edit messenger migrations, run the publish command with `messenger-migrations` tag.
54 |
55 | ```bash
56 | $ php artisan vendor:publish --tag messenger-migrations
57 | ```
58 | ### Views
59 |
60 | To publish and edit messenger views, run the publish command with `messenger-views` tag.
61 |
62 | ```bash
63 | $ php artisan vendor:publish --tag messenger-views
64 | ```
65 |
66 | ## TODO
67 |
68 | * emotions.
69 | * upload photos.
70 | * Attach files.
71 | * Show date before every conversation beginning.
72 | * paginate and load threads.
73 | * Laravel Messenger chatbox.
74 | * Unauthenticated chatbox to message customer service.
75 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | . threads doesn't reload page, use AJAX requests.
4 | . reorganize routes.
5 | . handle delete message fail.
6 | . handle load messages fail.
7 | . handle load threads fail.
8 | . handle make seen fail.
9 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "baklysystems/laravel-chat-messenger",
3 | "description": "Laravel chat package",
4 | "license": "MIT",
5 | "keywords": [
6 | "message",
7 | "inbox",
8 | "conversations",
9 | "chat",
10 | "php",
11 | "laravel",
12 | "realtime",
13 | "real-time",
14 | "talk",
15 | "fb chat",
16 | "like facebook chat",
17 | "like facebook messenger",
18 | "support chat"
19 | ],
20 | "homepage": "https://github.com/baklysystems/laravel-chat-messenger",
21 | "authors": [
22 | {
23 | "name": "Mohamed Abdul-Fattah",
24 | "email": "csmohamed8@gmail.com"
25 | }
26 | ],
27 | "require": {
28 | "laravel/framework": ">=5.3.0",
29 | "illuminate/support": ">=5.3.0",
30 | "laravelcollective/html": ">=5.3.0",
31 | "pusher/pusher-php-server": ">=2.6"
32 | },
33 | "autoload": {
34 | "psr-4": {
35 | "BaklySystems\\LaravelMessenger\\": "src/"
36 | }
37 | },
38 | "extra": {
39 | "laravel": {
40 | "providers": [
41 | "BaklySystems\\LaravelMessenger\\LaravelMessengerServiceProvider"
42 | ],
43 | "aliases": {
44 | "Messenger": "BaklySystems\\LaravelMessenger\\Facades\\Messenger"
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Facades/Messenger.php:
--------------------------------------------------------------------------------
1 | middleware(['web', 'auth']);
21 | }
22 |
23 | /**
24 | * Get messenger page.
25 | *
26 | * @param int $withId
27 | * @return Response
28 | */
29 | public function laravelMessenger($withId)
30 | {
31 | Messenger::makeSeen(auth()->id(), $withId);
32 | $withUser = config('messenger.user.model', 'App\User')::findOrFail($withId);
33 | $messages = Messenger::messagesWith(auth()->id(), $withUser->id);
34 | $threads = Messenger::threads(auth()->id());
35 |
36 | return view('messenger::messenger', compact('withUser', 'messages', 'threads'));
37 | }
38 |
39 | /**
40 | * Create a new message.
41 | *
42 | * @param \Illuminate\Http\Request $request
43 | * @return Response
44 | */
45 | public function store(Request $request)
46 | {
47 | $this->validate($request, Message::rules());
48 |
49 | $authId = auth()->id();
50 | $withId = $request->withId;
51 | $conversation = Messenger::getConversation($authId, $withId);
52 |
53 | if (! $conversation) {
54 | $conversation = Messenger::newConversation($authId, $withId);
55 | }
56 |
57 | $message = Messenger::newMessage($conversation->id, $authId, $request->message);
58 |
59 | // Pusher
60 | $pusher = new Pusher(
61 | config('messenger.pusher.app_key'),
62 | config('messenger.pusher.app_secret'),
63 | config('messenger.pusher.app_id'),
64 | [
65 | 'cluster' => config('messenger.pusher.options.cluster')
66 | ]
67 | );
68 | $pusher->trigger('messenger-channel', 'messenger-event', [
69 | 'message' => $message,
70 | 'senderId' => $authId,
71 | 'withId' => $withId
72 | ]);
73 |
74 | return response()->json([
75 | 'success' => true,
76 | 'message' => $message
77 | ], 200);
78 | }
79 |
80 | /**
81 | * Load threads view.
82 | *
83 | * @param \Illuminate\Http\Request $request
84 | * @return Response.
85 | */
86 | public function loadThreads(Request $request)
87 | {
88 | if ($request->ajax()) {
89 | $withUser = config('messenger.user.model', 'App\User')::findOrFail($request->withId);
90 | $threads = Messenger::threads(auth()->id());
91 | $view = view('messenger::partials.threads', compact('threads', 'withUser'))->render();
92 |
93 | return response()->json($view, 200);
94 | }
95 | }
96 |
97 | /**
98 | * Load more messages.
99 | *
100 | * @param \Illuminate\Http\Request $request
101 | * @return Response.
102 | */
103 | public function moreMessages(Request $request)
104 | {
105 | $this->validate($request, ['withId' => 'required|integer']);
106 |
107 | if ($request->ajax()) {
108 | $messages = Messenger::messagesWith(
109 | auth()->id(),
110 | $request->withId,
111 | $request->take
112 | );
113 | $view = view('messenger::partials.messages', compact('messages'))->render();
114 |
115 | return response()->json([
116 | 'view' => $view,
117 | 'messagesCount' => $messages->count()
118 | ], 200);
119 | }
120 | }
121 |
122 | /**
123 | * Make a message seen.
124 | *
125 | * @param \Illuminate\Http\Request $request
126 | * @return Response
127 | */
128 | public function makeSeen(Request $request)
129 | {
130 | Messenger::makeSeen($request->authId, $request->withId);
131 |
132 | return response()->json(['success' => true], 200);
133 | }
134 |
135 | /**
136 | * Delete a message.
137 | *
138 | * @param int $id
139 | * @return Response.
140 | */
141 | public function destroy($id)
142 | {
143 | $confirm = Messenger::deleteMessage($id, auth()->id());
144 |
145 | return response()->json(['success' => true], 200);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/LaravelMessengerServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
17 | // config file.
18 | __DIR__.'/config/messenger.php' => config_path('messenger.php'),
19 | // controller.
20 | __DIR__.'/Http/Controllers/MessageController.php'
21 | => app_path('Http/Controllers/MessageController.php'),
22 | // assets.
23 | __DIR__.'/assets' => public_path('vendor/messenger'),
24 | // routes
25 | __DIR__.'/routes/' => base_path('/routes/'),
26 | ]);
27 |
28 | // routes.
29 | // $this->loadRoutesFrom(base_path('routes/messenger.php'));
30 |
31 | // migrations.
32 | $this->loadMigrationsFrom(__DIR__.'/database/migrations');
33 | // publish under messenger-migrations tag.
34 | $this->publishes([
35 | __DIR__.'/database/migrations' => database_path('migrations'),
36 | ], 'messenger-migrations');
37 |
38 | // views.
39 | $this->loadViewsFrom(__DIR__.'/views', 'messenger');
40 | // publish under messenger-views tag.
41 | $this->publishes([
42 | __DIR__.'/views' => resource_path('views/vendor/messenger'),
43 | ], 'messenger-views');
44 | }
45 |
46 | /**
47 | * Register the application services.
48 | *
49 | * @return void
50 | */
51 | public function register()
52 | {
53 | // Messenger Facede.
54 | $this->app->singleton('messenger', function () {
55 | return new Messenger;
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Messenger.php:
--------------------------------------------------------------------------------
1 | whereUserOne($authId)
34 | ->whereUserTwo($withId);
35 | })->orWhere(function ($query) use ($authId, $withId) {
36 | $query->whereUserOne($withId)
37 | ->whereUserTwo($authId);
38 | })->first();
39 |
40 | return $conversation;
41 | }
42 |
43 | /**
44 | * Make a new conversation between two users.
45 | *
46 | * @param int $authId
47 | * @param int $withId
48 | * @return collection
49 | */
50 | public function newConversation($authId, $withId)
51 | {
52 | $conversation = Conversation::create([
53 | 'user_one' => $authId,
54 | 'user_two' => $withId
55 | ]);
56 |
57 | return $conversation;
58 | }
59 |
60 | /**
61 | * Get last {$take} conversations with all users for a user.
62 | *
63 | * @param int $authId
64 | * @param int $take (optional)
65 | * @return collection
66 | */
67 | public function userConversations($authId, $take = 20)
68 | {
69 | $collection = Conversation::whereUserOne($authId)
70 | ->orWhere('user_two', $authId);
71 | $totalRecords = $collection->count();
72 | $conversations = $collection->take($take)
73 | ->skip($totalRecords - $take)
74 | ->get();
75 |
76 | return $conversations;
77 | }
78 |
79 | /**
80 | * Create a new message between two users.
81 | *
82 | * @param int $conversationId
83 | * @param int $senderId
84 | * @param string $message
85 | * @param boolean $isSeen (optional)
86 | * @param boolean $deletedSender (optional)
87 | * @param boolean $deletedReceiver (optional)
88 | * @return collection
89 | */
90 | public function newMessage(
91 | $conversationId,
92 | $senderId,
93 | $message,
94 | $isSeen = 0,
95 | $deletedSender = 0,
96 | $deletedReceiver = 0
97 | )
98 | {
99 | $message = Message::create([
100 | 'conversation_id' => $conversationId,
101 | 'sender_id' => $senderId,
102 | 'message' => $message,
103 | 'is_seen' => $isSeen,
104 | 'deleted_from_sender' => $deletedSender,
105 | 'deleted_from_receiver' => $deletedReceiver
106 | ]);
107 |
108 | return $message;
109 | }
110 |
111 | /**
112 | * Get last {$take} messages between two users.
113 | *
114 | * @param int $authId
115 | * @param int $withId
116 | * @param int $take (optional)
117 | * @return collection
118 | */
119 | public function messagesWith($authId, $withId, $take = 20)
120 | {
121 | $conversation = $this->getConversation($authId, $withId);
122 |
123 | if ($conversation) {
124 | $messages = Message::whereConversationId($conversation->id)
125 | ->where(function ($query) use ($authId, $withId) {
126 | $query->where(function ($qr) use ($authId) {
127 | $qr->where('sender_id', $authId) // this message is sent by the authUser.
128 | ->where('deleted_from_sender', 0);
129 | })->orWhere(function ($qr) use ($withId) {
130 | $qr->where('sender_id', $withId) // this message is sent by the receiver/withUser.
131 | ->where('deleted_from_receiver', 0);
132 | });
133 | })
134 | ->latest()
135 | ->take($take)
136 | ->get();
137 |
138 | return $messages->reverse();
139 | }
140 |
141 | return collect();
142 | }
143 |
144 | /**
145 | * Get last {$take} user threads with all other users.
146 | *
147 | * @param int $authId
148 | * @param int $take (optional)
149 | * @return collection
150 | */
151 | public function threads($authId, $take = 20)
152 | {
153 | $conversations = $this->userConversations($authId, $take);
154 | $threads = [];
155 |
156 | foreach ($conversations as $key => $conversation) {
157 | if ($conversation->user_one === $authId) {
158 | $withUser = $conversation->userTwo;
159 | } else {
160 | $withUser = $conversation->userOne;
161 | }
162 | $collection = (object) null;
163 | $collection->conversationId = $conversation->id;
164 | $collection->withUser = $withUser;
165 | $collection->lastMessage = $conversation->lastMessage();
166 | $threads[] = $collection;
167 | }
168 |
169 | $threads = collect($threads);
170 | $threads = $threads->sortByDesc(function ($ins, $key) { // order threads by last updated message.
171 | $ins = (array) $ins;
172 | return $ins['lastMessage']['created_at'];
173 | });
174 |
175 | return $threads->values()->all();
176 | }
177 |
178 | /**
179 | * Make messages seen for a conversation.
180 | *
181 | * @param int $authId
182 | * @param int $withId
183 | * @return boolean
184 | */
185 | public function makeSeen($authId, $withId)
186 | {
187 | $conversation = $this->getConversation($authId, $withId);
188 | if ($conversation) {
189 | Message::whereConversationId($conversation->id)->update([
190 | 'is_seen' => 1
191 | ]);
192 | }
193 |
194 | return response()->json(['success' => true], 200);
195 | }
196 |
197 | /**
198 | * Delete a message.
199 | *
200 | * @param int $messageId
201 | * @param int $authId
202 | * @return boolean.
203 | */
204 | public function deleteMessage($messageId, $authId)
205 | {
206 | $message = Message::findOrFail($messageId);
207 | if ($message->sender_id == $authId) {
208 | $message->update(['deleted_from_sender' => 1]);
209 | } else {
210 | $message->update(['deleted_from_receiver' => 1]);
211 | }
212 |
213 | return response()->json(['success' => true], 200);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/Models/Conversation.php:
--------------------------------------------------------------------------------
1 | hasMany('BaklySystems\LaravelMessenger\Models\Message', 'conversation_id');
45 | }
46 |
47 | /**
48 | * Get conversation first user
49 | *
50 | * @return collection
51 | */
52 | public function userOne()
53 | {
54 | return $this->belongsTo(config('messenger.user.model', 'App\User'), 'user_one');
55 | }
56 |
57 | /**
58 | * Get conversation second user.
59 | *
60 | * @return collection
61 | */
62 | public function userTwo()
63 | {
64 | return $this->belongsTo(config('messenger.user.model', 'App\User'), 'user_two');
65 | }
66 |
67 | /**
68 | * Get conversation last message for threads.
69 | *
70 | * @return collection
71 | */
72 | public function lastMessage()
73 | {
74 | return $this->messages->last();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Models/Message.php:
--------------------------------------------------------------------------------
1 | 'integer',
30 | 'withId' => 'required|integer', // message reciever.
31 | 'message' => 'required|string',
32 | 'is_seen' => 'boolean',
33 | 'deleted_from_sender' => 'boolean',
34 | 'deleted_from_receiver' => 'boolean'
35 | ];
36 |
37 | /**
38 | * The rules getter.
39 | *
40 | * @return array
41 | */
42 | public static function rules()
43 | {
44 | return self::$rules;
45 | }
46 |
47 | /**
48 | * Get message conversation.
49 | *
50 | * @return collection
51 | */
52 | public function conversation()
53 | {
54 | return $this->belongsTo('BaklySystems\LaravelMessenger\Models\Conversation');
55 | }
56 |
57 | /**
58 | * Get message sender.
59 | *
60 | * @return collection
61 | */
62 | public function sender()
63 | {
64 | return $this->belongsTo(config('messenger.user.model', config('messenger.user.model', 'App\User')));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/assets/css/messenger.css:
--------------------------------------------------------------------------------
1 | .messenger {
2 | height: 24em;
3 | overflow-y: scroll;
4 | overflow-x: hidden;
5 | }
6 |
7 | /*Send Box*/
8 |
9 | #message-body {
10 | width: 100%;
11 | border: 1px solid lightgray;
12 | padding: 10px 20px;
13 | margin-bottom: 10px;
14 | border-radius: 5px;
15 | resize: none;
16 | }
17 |
18 | /*Headers*/
19 |
20 | .panel-default>.panel-heading {
21 | background-color: #2a88bd;
22 | color: #fff;
23 | }
24 |
25 | /*Profile*/
26 |
27 | .panel-default>.panel-body p span {
28 | color: #ccc;
29 | font-weight: bold;
30 | }
31 |
32 | /*Messages*/
33 |
34 | .messenger-body div p {
35 | color: #fff;
36 | padding: 5px 1em;
37 | margin: 3px 1em;
38 | width: fit-content;
39 | width: -moz-fit-content;
40 | max-width: 80%;
41 | border-radius: 5px;
42 | }
43 |
44 | .sent {
45 | background-color: #63b4dc;
46 | float: right;
47 | }
48 |
49 | .received {
50 | background-color: #ccc;
51 | float: left;
52 | }
53 |
54 | .unsent {
55 | color: #f03d25;
56 | float: right;
57 | cursor: pointer;
58 | }
59 |
60 | /*Threads*/
61 |
62 | .thread-link:hover {
63 | text-decoration: none;
64 | }
65 |
66 | .thread-row {
67 | background-color: #fff;
68 | padding-top: 8px;
69 | }
70 |
71 | .thread-row:hover, .current {
72 | background-color: #f3f3f2;
73 | }
74 |
75 | .thread-user {
76 | color: #000;
77 | padding: 0 1em;
78 | }
79 |
80 | .thread-message {
81 | padding: 0 1em;
82 | color: #999999;
83 | font-size: 13px;
84 | }
85 |
86 | .unseen {
87 | font-weight: bold;
88 | }
89 | .unseen p.thread-message {
90 | color: #333;
91 | }
92 | /*Messages Preloader*/
93 |
94 | #messages-preloader {
95 | border: 2px solid #f3f3f3;
96 | border-top: 2px solid #3498db;
97 | border-radius: 50%;
98 | width: 20px;
99 | height: 20px;
100 | animation: spin 2s linear infinite;
101 | margin: auto;
102 | }
103 |
104 | @keyframes spin {
105 | 0% {
106 | transform: rotate(0deg);
107 | }
108 | 100% {
109 | transform: rotate(360deg);
110 | }
111 | }
112 |
113 | .start-conv {
114 | text-align: center;
115 | color: #ccc;
116 | }
117 |
118 | /*Menu Dots*/
119 |
120 | .fa-ellipsis-h {
121 | color: #eee;
122 | display: none;
123 | position: relative;
124 | }
125 |
126 | .fa-ellipsis-h:hover {
127 | cursor: pointer;
128 | }
129 |
130 | .delete {
131 | display: none;
132 | position: absolute;
133 | bottom: 30px;
134 | right: 5px;
135 | background-color: #000;
136 | color: #fff;
137 | opacity: 0.8;
138 | font-size: 18px;
139 | border-radius: 5px;
140 | padding: 10px;
141 | }
142 |
--------------------------------------------------------------------------------
/src/assets/js/messenger-chat.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This JS file applies only for messenger page.
3 | */
4 | ;(function () {
5 | var take = (messagesCount < 20) ? 0 : 20, // represents the number of messages to be displayed.
6 | $messenger = $('.messenger'),
7 | $loader = $('#messages-preloader'),
8 | channel = pusher.subscribe('messenger-channel');
9 |
10 | /**
11 | * Do action when a message event is triggered.
12 | */
13 | channel.bind('messenger-event', function (data) {
14 | if (data.senderId == withId && data.withId == authId && data.withId != data.senderId) { // current conversation thread.
15 | newMessage(data.message, 'received');
16 | playTweet();
17 | makeSeen(authId, withId);
18 | } else if (data.withId == authId && data.withId != data.senderId) { // not opened thread.
19 | playTweet();
20 | loadThreads();
21 | }
22 | });
23 |
24 | /**
25 | * Make a message seen.
26 | */
27 | function makeSeen(authId, withId) {
28 | $.ajax({
29 | url: '/messenger/ajax/make-seen',
30 | method: 'POST',
31 | data: {authId: authId, withId: withId}
32 | }).done(function (res) {
33 | if (res.success) {
34 | loadThreads();
35 | }
36 | });
37 | }
38 |
39 | /**
40 | * Delete confirmation.
41 | */
42 | function confirmDelete() {
43 | return confirm('Are your sure you want to delete this message');
44 | }
45 |
46 | /**
47 | * Create a new menu for the new message.
48 | */
49 | function newMenu(messageClass, messageId) {
50 | var pull = (messageClass === 'sent') ? 'pull-right' : 'pull-left';
51 |
52 | return '\
53 | \
54 |
Conversation started
'); 125 | $loader.remove(); 126 | } 127 | }); 128 | } 129 | 130 | /** 131 | * Play message notification sound. 132 | */ 133 | function playTweet() { 134 | var audio = new Audio('/vendor/messenger/sounds/tweet.mp3'); 135 | audio.play(); 136 | } 137 | 138 | $(document).ready(function () { 139 | $.ajaxSetup({ 140 | headers: { 141 | 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') 142 | } 143 | }); 144 | 145 | scrollMessagesDown(); 146 | 147 | /** 148 | * Send message to backend and handle responses. 149 | */ 150 | $(document).on('click', '#send-btn', function (e) { 151 | var message = $('#message-body').val(); 152 | 153 | if (message) { 154 | var JqHXR = $.ajax({ 155 | url: '/messenger/send', 156 | method: 'POST', 157 | data: { 158 | message: message, 159 | withId: withId 160 | } 161 | }); 162 | } 163 | JqHXR.done(function (res) { // message sent. 164 | if (res.success) { 165 | newMessage(res.message, 'sent'); 166 | loadThreads(); 167 | } 168 | }); 169 | JqHXR.fail(function (res) { // message didn't send. 170 | newMessage(res.message, 'sent', true); 171 | }); 172 | }); 173 | 174 | /** 175 | * Load more messages when scroll to top. 176 | */ 177 | $messenger.on('scroll', function (e) { 178 | if (!$messenger.scrollTop() && take) { 179 | take += 20; 180 | loadMessages(); 181 | } 182 | }); 183 | 184 | /** 185 | * Hover to messages to show menu dots. 186 | */ 187 | $(document).on('mouseover', '.message-row', function (e) { 188 | $(this).find('.fa-ellipsis-h').show(); 189 | }); 190 | 191 | /** 192 | * Mouse up to remove menu dots. 193 | */ 194 | $(document).on('mouseout', '.message-row', function (e) { 195 | var deleteBtn = $(this).find('.delete').css('display'); 196 | 197 | if (deleteBtn !== 'block') { 198 | $(this).find('.fa-ellipsis-h').hide(); // only hide if delete popup is not poped up. 199 | } 200 | }); 201 | 202 | /** 203 | * CLick on menu dots to show up delete message option. 204 | */ 205 | $(document).on('click', '.fa-ellipsis-h', function (e) { 206 | // Hide all other opened menus. 207 | $('.delete').not($(this).find('.delete')).hide(); 208 | $('.fa-ellipsis-h').not($(this).find('.fa-ellipsis-h')).hide(); 209 | // Only show this menu. 210 | $(this).find('.delete').toggle(); 211 | }); 212 | 213 | /** 214 | * Hide opened delete menu when click anywhere. 215 | */ 216 | $('body').on('click', function (e) { 217 | if ($(e.target).hasClass('fa-ellipsis-h')) { // toggle delete menu on clicking on dots. 218 | return true; 219 | } else if ($(e.target).hasClass('message-row')) { // don't hide on hover dots when click on message row. 220 | $('.fa-ellipsis-h').not($(e.target).find('.fa-ellipsis-h')).hide(); 221 | } else { 222 | $('.fa-ellipsis-h').hide(); 223 | } 224 | $('.delete').hide(); 225 | }); 226 | 227 | /** 228 | * Delete message. 229 | */ 230 | $(document).on('click', '.delete', function (e) { 231 | var confirm = confirmDelete(); 232 | if (confirm) { 233 | var id = $(this).attr('data-id'), 234 | $message = $(this).parent().parent(); 235 | 236 | $.ajax({ 237 | url: '/messenger/delete/' + id, 238 | method: 'DELETE' 239 | }).done(function (res) { 240 | if (res.success) { 241 | $message.hide(function () { 242 | $message.remove(); 243 | }); 244 | } 245 | }); 246 | } 247 | }); 248 | }); 249 | }()); 250 | -------------------------------------------------------------------------------- /src/assets/sounds/tweet.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baklysystems/laravel-chat-messenger/65b9c7e896a1f5c3bd8a8b62363895108b88f7d4/src/assets/sounds/tweet.mp3 -------------------------------------------------------------------------------- /src/config/messenger.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'model' => 'App\User' 17 | ], 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Messenger Pusher Keys 22 | |-------------------------------------------------------------------------- 23 | | 24 | | This option defines pusher keys. 25 | | 26 | */ 27 | 28 | 'pusher' => [ 29 | 'app_id' => '', 30 | 'app_key' => '', 31 | 'app_secret' => '', 32 | 'options' => [ 33 | 'cluster' => '', 34 | 'encrypted' => true 35 | ] 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /src/database/migrations/2017_10_24_140229_create_conversaions_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_one'); 19 | $table->unsignedInteger('user_two'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('conversations'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/database/migrations/2017_10_24_140250_create_messages_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('conversation_id'); 19 | $table->unsignedInteger('sender_id'); 20 | $table->text('message'); 21 | $table->boolean('is_seen')->default(0); 22 | $table->boolean('deleted_from_sender')->default(0); 23 | $table->boolean('deleted_from_receiver')->default(0); 24 | $table->foreign('conversation_id')->references('id')->on('conversations'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('messages'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/routes/messenger.php: -------------------------------------------------------------------------------- 1 | group(function () { 8 | Route::get('t/{id}', 'App\Http\Controllers\MessageController@laravelMessenger')->name('messenger'); 9 | Route::post('send', 'App\Http\Controllers\MessageController@store')->name('message.store'); 10 | Route::get('threads', 'App\Http\Controllers\MessageController@loadThreads')->name('threads'); 11 | Route::get('more/messages', 'App\Http\Controllers\MessageController@moreMessages')->name('more.messages'); 12 | Route::delete('delete/{id}', 'App\Http\Controllers\MessageController@destroy')->name('delete'); 13 | // AJAX requests. 14 | Route::prefix('ajax')->group(function () { 15 | Route::post('make-seen', 'App\Http\Controllers\MessageController@makeSeen')->name('make-seen'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/views/messenger.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('css-styles') 4 | 5 | 6 | @endsection 7 | 8 | @section('content') 9 |Conversation started
29 | @endif 30 |50 | Name {{$withUser->name}} 51 |
52 |53 | Email {{$withUser->email}} 54 |
55 |20 | {{$thread->withUser->name}} 21 |
22 | 28 |