PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 All 36 releases
vikbooking / admin / layouts / chat / threads.php

threads.php in VikBooking Hotel Booking Engine & PMS trunk, at admin/layouts/chat/threads.php

338 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - E4J srl
6 * @copyright Copyright (C) 2025 E4J srl. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Display data attributes.
15 *
16 * @var array $threads
17 * @var string[] $categories
18 * @var array $options
19 * @var string $id
20 */
21 extract($displayData);
22
23 $id = 'vbo-chat-interface-' . ($options['id'] ?? uniqid());
24
25 ?>
26
27 <div class="vbo-chat-interface<?php echo ($options['compact'] ?? false) ? ' compact' : ''; ?>" id="<?php echo $id; ?>">
28
29 <div class="vbo-chat-threads">
30
31 <?php
32 foreach ($threads as $thread) {
33 echo $this->sublayout('thread', [
34 'thread' => $thread,
35 'options' => $options ?? [],
36 ]);
37 }
38 ?>
39
40 </div>
41
42 <div class="vbo-chat-target">
43 <?php
44 echo JLayoutHelper::render('chat.blank', [
45 'title' => '',
46 'subtitle' => JText::translate('VBO_CHAT_CONV_EMPTY_SUBTITLE'),
47 ]);
48 ?>
49 </div>
50
51 <a href="javascript:void(0)" class="vbo-chat-back"><?php VikBookingIcons::e('chevron-left'); ?>&nbsp;<?php echo JText::translate('VBBACK'); ?></a>
52
53 </div>
54
55 <script>
56 (function($) {
57 'use strict';
58
59 const rearrangeThreads = () => {
60 const threads = $('#<?php echo $id; ?>').find('.vbo-chat-threads').children().sort((a, b) => {
61 // get values to compare
62 const x = $(a).attr('data-date');
63 const y = $(b).attr('data-date');
64
65 // equal by default
66 let delta = 0;
67
68 if (x < y) {
69 // A is lower than B
70 delta = 1;
71 } else if (x > y) {
72 // A is highet than B
73 delta = -1;
74 }
75
76 return delta;
77 });
78
79 $('#<?php echo $id; ?>').find('.vbo-chat-threads').html(threads);
80 }
81
82 const refreshThread = (chat) => {
83 const context = chat.data.environment.context;
84 const thread = $('#<?php echo $id; ?>').find('.chat-thread[data-context="' + context.alias + '"][data-id="' + context.id + '"]');
85
86 if (!thread.length) {
87 return;
88 }
89
90 const lastMessage = chat.getLatestMessage();
91
92 // obtain the details of the user that wrote the last message
93 const user = chat.getMessageUser(lastMessage);
94
95 // refresh avatar
96 thread.find('.chat-thread-avatar').html(chat.drawUserAvatar(user));
97
98 // refresh user name
99 thread.find('.message-author').text(user.name);
100
101 // refresh date
102 const date = DateHelper.stringToDate(lastMessage.createdon);
103 thread.attr('data-date', lastMessage.createdon);
104 thread.find('.last-update-time').text(date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
105
106 if (DateHelper.isToday(date)) {
107 thread.find('.last-update-date').text(Joomla.JText._('VBTODAY'));
108 } else if (DateHelper.isYesterday(date)) {
109 thread.find('.last-update-date').text(Joomla.JText._('VBOYESTERDAY'));
110 } else {
111 thread.find('.last-update-date').text(date.toLocaleDateString());
112 }
113
114 // strip HTML tags from message
115 const tmpDiv = document.createElement("div");
116 tmpDiv.innerHTML = lastMessage.message;
117 let plainMessageText = tmpDiv.textContent || tmpDiv.innerText || '';
118
119 // refresh message
120 thread.find('.chat-thread-message-body').text(shortenText(plainMessageText, 80)).attr('data-length', plainMessageText.length);
121
122 // refresh attachments
123 const attachments = lastMessage.attachments.map(a => a.name).join(', ');
124 thread.find('.chat-thread-message-attachments').attr('data-length', attachments.length).find('span').text(shortenText(attachments, 80));
125
126 rearrangeThreads();
127 }
128
129 const shortenText = (text, max) => {
130 // Check whether we should take a substring of the text.
131 // Reserve an additional 25% of characters to avoid breaking the
132 // text too close to the end of the string.
133 if (max && text.length > max * 1.25) {
134 // explode the string in words
135 let chunks = text.split(' ');
136
137 text = '';
138
139 // keep adding words until we reach the maximum threshold
140 while (chunks && text.length < max) {
141 text += chunks.shift() + ' ';
142 }
143
144 // get rid of trailing special characters and add the ellipsis
145 text = text.replace(/[.,?!;:#'"([{ ]+$/, '') + '...';
146 }
147
148 return text;
149 }
150
151 $(document).on('click', '#<?php echo $id; ?> .chat-thread[data-context][data-id]', function() {
152 if ($(this).hasClass('active')) {
153 return false;
154 }
155
156 // always destroy and previously open chat
157 VBOChat.getInstance().destroy();
158
159 $('#<?php echo $id; ?>').find('.chat-thread[data-context][data-id].active').removeClass('active');
160 $(this).addClass('active');
161
162 // remove any loading overlay previously appended
163 $('#<?php echo $id; ?>').find('.vbo-chat-target .vbo-chat-loading').remove();
164
165 // append loading box to the chat target
166 $('#<?php echo $id; ?>').find('.vbo-chat-target').append(
167 $('<div class="vbo-chat-loading"><?php VikBookingIcons::e('circle-notch', 'fa-spin fa-3x'); ?></div>')
168 ).addClass('slide-in');
169
170 VBOChatAjax.do(
171 '<?php echo VBOFactory::getPlatform()->getUri()->ajax('index.php?option=com_vikbooking&task=chat.render_chat'); ?>',
172 {
173 context: $(this).data('context'),
174 id_context: $(this).data('id'),
175 },
176 (resp) => {
177 $('#<?php echo $id; ?>').find('.vbo-chat-target').html(resp.html);
178 },
179 (err) => {
180 // update icon on loading overlay
181 $('#<?php echo $id; ?>').find('.vbo-chat-target .vbo-chat-loading').html(
182 '<?php VikBookingIcons::e('exclamation-triangle', 'fa-3x'); ?>'
183 );
184
185 setTimeout(() => {
186 alert(err.responseText || err.statusText || 'Connection lost!');
187 }, 32);
188 }
189 );
190 });
191
192 window.addEventListener('chat.sync', (event) => {
193 refreshThread(event.detail.chat);
194 });
195
196 window.addEventListener('chat.send', (event) => {
197 refreshThread(event.detail.chat);
198 });
199
200 window.addEventListener('chat.read', (event) => {
201 const context = event.detail.chat.data.environment.context;
202 const thread = $('#<?php echo $id; ?>').find('.chat-thread[data-context="' + context.alias + '"][data-id="' + context.id + '"]');
203
204 if (!thread.length) {
205 return;
206 }
207
208 // mark thread as read
209 thread.attr('data-read', 1);
210 });
211
212 let isLoadingOlderThreads = false;
213 let totalThreads = <?php echo count($threads); ?>;
214 let threadsLimit = <?php echo $options['limit'] ?? 20; ?>;
215
216 const createLoadingSkeleton = (count) => {
217 let skeleton = '';
218
219 for (let i = 1; i <= count; i++) {
220 skeleton += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton chat-thread">';
221 skeleton += ' <div class="vbo-dashboard-guest-activity-avatar">';
222 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
223 skeleton += ' </div>';
224 skeleton += ' <div class="vbo-dashboard-guest-activity-content chat-thread-content">';
225 skeleton += ' <div class="vbo-dashboard-guest-activity-content-head">';
226 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
227 skeleton += ' </div>';
228 skeleton += ' <div class="vbo-dashboard-guest-activity-content-subhead">';
229 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>';
230 skeleton += ' </div>';
231 skeleton += ' <div class="vbo-dashboard-guest-activity-content-info-msg">';
232 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
233 skeleton += ' </div>';
234 skeleton += ' </div>';
235 skeleton += '</div>';
236 }
237
238 return skeleton;
239 }
240
241 const loadPreviousThreads = () => {
242 if (isLoadingOlderThreads) {
243 // do not proceed in case we are already loading something
244 return this;
245 }
246
247 // mark loading flag
248 isLoadingOlderThreads = true;
249
250 const threadsList = $('#<?php echo $id; ?>').find('.vbo-chat-threads');
251 threadsList.append(createLoadingSkeleton(5));
252
253 // make AJAX request to load older threads
254 VBOChatAjax.do(
255 // end-point URL
256 '<?php echo VBOFactory::getPlatform()->getUri()->ajax('index.php?option=com_vikbooking&task=chat.load_chats'); ?>',
257 // POST data
258 {
259 start: totalThreads,
260 limit: threadsLimit,
261 categories: <?php echo json_encode($categories ?? []); ?>,
262 options: <?php echo json_encode($options ?? []); ?>,
263 },
264 // success callback
265 (threads) => {
266 // keep current scroll
267 let currentScrollTop = threadsList[0].scrollTop;
268 let currentScrollHeight = threadsList[0].scrollHeight;
269
270 // remove loading skeleton
271 threadsList.find('.vbo-dashboard-guest-activity-skeleton').remove();
272
273 threads.forEach((thread) => {
274 const existing = $('#<?php echo $id; ?>').find('.chat-thread[data-context="' + thread.alias + '"][data-id="' + thread.id + '"]');
275
276 if (!existing.length) {
277 // add thread only in case it is not already in the list
278 threadsList.append(thread.html);
279 }
280 });
281
282 // update count of loaded threads
283 totalThreads += threads.length;
284
285 // turn off scroll event in case we reached the limit
286 if (threads.length < threadsLimit) {
287 threadsList.off('scroll');
288 }
289
290 // make loading available again
291 isLoadingOlderThreads = false;
292 },
293 // failure callback
294 (error) => {
295 // remove loading skeleton
296 threadsList.find('.vbo-dashboard-guest-activity-skeleton').remove();
297 // make loading available again
298 isLoadingOlderThreads = false;
299 }
300 );
301 }
302
303 $(function() {
304 // do not register scroll event in case the number of messages is equal or
305 // higher then the total number of messages under this context
306 if (totalThreads >= threadsLimit) {
307 // setup scroll event to load older messages
308 $('#<?php echo $id; ?>').find('.vbo-chat-threads').on('scroll', function() {
309 if (isLoadingOlderThreads) {
310 // ignore if we are currently loading older messages
311 return;
312 }
313
314 // get scrollable pixel
315 const scrollHeight = this.scrollHeight - $(this).outerHeight();
316 // get scroll top
317 const scrollTop = this.scrollTop;
318
319 // start loading older threads only when scrollbar
320 // is 300px close to the end
321 if (scrollHeight - scrollTop <= 300) {
322 loadPreviousThreads();
323 }
324 });
325 }
326
327 $('#<?php echo $id; ?>').find('.vbo-chat-back').on('click', function() {
328 $(this).hide();
329 $('#<?php echo $id; ?>').find('.chat-thread[data-context][data-id].active').removeClass('active');
330 $(this).prev().removeClass('slide-in');
331
332 setTimeout(() => {
333 $(this).show();
334 }, 300);
335 });
336 });
337 })(jQuery);
338 </script>