blob: f4df001fa2c64b7b0ecff961e5c3414ac3acf1f5 [file] [log] [blame]
aviau039001d2016-09-29 16:39:05 -04001<html>
aviau039001d2016-09-29 16:39:05 -04002<!-- Empty head might be needed for setSenderImage -->
3<head>
Frederic Guimontd8343e62016-11-01 23:31:25 -04004 <meta name="viewport" content="width=device-width, initial-scale=1" />
Frédéric Guimont13778a62016-11-02 21:21:21 -04005 <meta charset=“utf-8”>
aviau039001d2016-09-29 16:39:05 -04006</head>
7
8<body>
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04009 <div class="navbar-wrapper">
10 <div id="navbar">
11 <div id="backButton" class="nav-button non-action-button nav-left" onmouseover="addBackButtonHoverProperty()" onclick="backToWelcomeView()" title="Hide chat view">
12 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
13 <path d="M11.67 3.87L9.9 2.1 0 12l9.9 9.9 1.77-1.77L3.54 12z"/>
14 <path fill="none" d="M0 0h24v24H0z"/>
15 </svg>
16 </div>
17 <div id="nav-contactid" class="nav-left">
18 <div id="nav-contactid-alias"></div>
19 <div id="nav-contactid-bestId"></div>
20 </div>
21 <div style="display:none" class="deactivated nav-button action-button nav-right" onclick="moreOptions()" title="Options">
22 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
23 <path d="M0 0h24v24H0z" fill="none"/>
24 <path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
25 </svg>
26 </div>
27 <div id="callButtons"> <!-- callButtons block allows more efficient hiding of placeCallButton and placeAudioCallButton -->
28 <div id="placeCallButton" class="nav-button action-button nav-right" onclick="placeCall()" title="Place video call">
29 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
30 <path d="M0 0h24v24H0z" fill="none"/>
31 <path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/>
32 </svg>
33 </div>
34 <div id="placeAudioCallButton" class="nav-button action-button nav-right" onclick="placeAudioCall()" title="Place audio call">
35 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
36 <path fill="none" d="M0 0h24v24H0z"/>
37 <path d="M20.01 15.38c-1.23 0-2.42-.2-3.53-.56-.35-.12-.74-.03-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z"/>
38 </svg>
39 </div>
40 </div>
41 <div id="addToConversationsButton" class="nav-button action-button nav-right" onclick="addToConversations()" title="Add to conversations">
42 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
43 <path d="M0 0h24v24H0z" fill="none"/>
44 <path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
45 </svg>
46 </div>
47 <div id="addBannedContactButton" class="nav-button action-critical-button nav-right" onclick="addBannedContact()" title="Unban banned contact">
48 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
49 <path fill="none" d="M0 0h24v24H0V0z"/>
50 <circle cx="15" cy="8" r="4"/>
51 <path d="M23 20v-2c0-2.3-4.1-3.7-6.9-3.9l6 5.9h.9zm-11.6-5.5C9.2 15.1 7 16.3 7 18v2h9.9l4 4 1.3-1.3-21-20.9L0 3.1l4 4V10H1v2h3v3h2v-3h2.9l2.5 2.5zM6 10v-.9l.9.9H6z"/>
52 </svg>
53 </div>
54 </div>
55 <div id="invitation">
56 <div id="text">
57 </div>
58 <div id="actions">
59 <div id="accept-btn" class="invitation-button button-green" onclick="acceptInvitation()" >Accept</div>
60 <div id="refuse-btn" class="invitation-button button-red" onclick="refuseInvitation()" >Refuse</div>
61 <div id="block-btn" class="invitation-button button-red" onclick="blockConversation()" >Block</div>
62 </div>
63 </div>
64 </div>
Adrien Beraud8e25afb2017-04-19 01:38:57 +020065 <div id="container">
66 <div id="messages"></div>
AmarOkb4253242017-07-13 11:21:39 -040067 <div id="sendMessage">
Hugo Lefeuvreedad8832018-05-14 16:36:06 -040068 <div class="nav-button action-button" onclick="sendFile()" title="Send File">
Hugo Lefeuvre8127fe12018-05-23 10:53:12 -040069 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
70 <path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/>
71 <path d="M0 0h24v24H0z" fill="none"/>
72 </svg>
AmarOkb4253242017-07-13 11:21:39 -040073 </div>
Hugo Lefeuvre8127fe12018-05-23 10:53:12 -040074 <textarea id="message" autofocus placeholder="Type a message" onkeyup="grow_text_area()" rows="1" disabled="false"></textarea>
Hugo Lefeuvreedad8832018-05-14 16:36:06 -040075 <div class="nav-button action-button" onclick="sendMessage()" title="Send">
Hugo Lefeuvre8127fe12018-05-23 10:53:12 -040076 <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
77 <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
78 <path d="M0 0h24v24H0z" fill="none"/>
79 </svg>
Guillaume Roguez5b137be2018-02-21 10:44:58 -050080 </div>
AmarOkb4253242017-07-13 11:21:39 -040081 </div>
Adrien Beraud8e25afb2017-04-19 01:38:57 +020082 </div>
aviau039001d2016-09-29 16:39:05 -040083</body>
84
aviau039001d2016-09-29 16:39:05 -040085<script>
AmarOk6286ad42017-07-14 12:11:08 -040086
Hugo Lefeuvreedad8832018-05-14 16:36:06 -040087/* Constants used at several places*/
88const messageBarPlaceHolder = "Type a message";
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -040089const avatar_size = 35;
90var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
91var displayLinksEnabled = false;
92var historyBufferIndex = 0; // When showing a large amount of interactions, this counter store the interaction's index to show
93var historyBuffer = []; // Before showing a large amount of interactions, this array is used as a buffer.
94var hoverBackButtonAllowed = true;
95
96/* We retrieve refs to the most used navbar and message bar elements for efficiency purposes */
97/* NOTE: always use getElementById when possible, way more efficient */
Hugo Lefeuvreedad8832018-05-14 16:36:06 -040098const backButton = document.getElementById("backButton");
99const aliasField = document.getElementById("nav-contactid-alias");
100const bestIdField = document.getElementById("nav-contactid-bestId");
101const idField = document.getElementById("nav-contactid");
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400102const messageBar = document.getElementById('sendMessage');
103const messageBarInput = document.getElementById("message");
104const messages = document.getElementById("messages");
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400105const addToConvButton = document.getElementById("addToConversationsButton");
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400106const invitation = document.getElementById("invitation");
107const invitationText = document.getElementById('text');
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400108const callButtons = document.getElementById("callButtons");
109const addBannedContactButton = document.getElementById("addBannedContactButton");
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400110
111/* States: allows us to avoid re-doing something if it isn't meaningful */
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400112var isBanned = false;
113var isTemporary = false;
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400114var hasInvitation = false;
115
116messageBarInput.addEventListener("keydown", function (e) {
AmarOk6286ad42017-07-14 12:11:08 -0400117 e = e || event;
118 var map = {};
119 map[e.keyCode] = e.type == 'keydown';
120 if (e.ctrlKey || e.shiftKey) {
121 return true;
122 }
123 if (map[13]) {
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400124 sendMessage();
AmarOk6286ad42017-07-14 12:11:08 -0400125 e.preventDefault();
126 }
127 return true;
128});
129
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400130/* Update general view info */
131function update_chatview_frame(banned, temporary, alias, bestid) {
132 navbar.style.display = "none";
AmarOk6286ad42017-07-14 12:11:08 -0400133
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400134 hoverBackButtonAllowed = true;
AmarOk6286ad42017-07-14 12:11:08 -0400135
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400136 aliasField.innerHTML = (alias ? alias : bestid);
137
138 if(alias) {
139 bestIdField.innerHTML = bestid;
140 idField.classList.remove("oneEntry");
141 } else {
142 idField.classList.add("oneEntry");
143 }
144
145 if (isBanned !== banned) {
146 isBanned = banned;
147 hideMessageBar(banned);
148
149 if(banned) {
150 // contact is banned. update navbar and states
151 navbar.classList.add('onBannedState');
152 } else {
153 navbar.classList.remove('onBannedState');
154 }
155 } else if (isTemporary !== temporary) {
156 isTemporary = temporary;
157 if (temporary) {
158 addToConvButton.style.display = 'flex';
159 messageBarInput.placeholder = "Note: an interaction will create a new contact.";
160 } else {
161 addToConvButton.style.display = '';
162 messageBarInput.placeholder = messageBarPlaceHolder;
163 }
164 }
165
166 navbar.style.display = "";
167}
168
169/**
170 * Hide or show invitation. Invitation is hidden if no contactAlias/invalid alias is passed.
171 * Otherwise, invitation div is updated.
172 * @param contactAlias
173 */
174function showInvitation(contactAlias) {
175 if (!contactAlias) {
176 if (hasInvitation) {
177 hasInvitation = false;
178 invitation.style.visibility = '';
179 }
180 } else {
181 hasInvitation = true;
182 invitationText.innerHTML = '<h1>' + contactAlias + ' sends you an invitation</h1>'
183 + 'Do you want to add them to the conversations list?<br>'
184 + 'Note: you can automatically accept this invitation by sending a message.';
185 invitation.style.visibility = 'visible';
186 }
187}
188
189/**
190 * Hide or show navbar, and update body top padding accordingly.
191 */
192function displayNavbar(isVisible)
193{
194 if (isVisible) {
195 navbar.classList.remove("hiddenState");
196 document.documentElement.style.setProperty('--navbar-size', undefined);
197 } else {
198 navbar.classList.add("hiddenState");
199 document.documentElement.style.setProperty('--navbar-size', '0');
AmarOk6286ad42017-07-14 12:11:08 -0400200 }
201}
202
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400203/**
204 * Hide or show message bar, and update body bottom padding accordingly.
205 */
206function hideMessageBar(isHidden) {
207 if (isHidden) {
208 messageBar.classList.add("hiddenState");
209 document.documentElement.style.setProperty('--messagebar-size', '0');
210 } else {
211 messageBar.classList.remove("hiddenState");
212 document.documentElement.style.removeProperty('--messagebar-size');
213 }
214}
215
216function grow_text_area() {
217 var is_at_bottom = messages.scrollTop === (messages.scrollHeight - messages.offsetHeight);
218
219 var old_height = messageBarInput.style.height;
220 messageBarInput.style.height = "auto";
221 messageBarInput.style.height = messageBarInput.scrollHeight +"px";
222
223 if (is_at_bottom) {
224 messages.scrollTop = messages.scrollHeight;
225 }
226}
227
Sébastien Blinc6f3a262017-07-28 16:12:24 -0400228/*
229 * Update timestamps messages
230 */
231function updateView() {
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400232 updateTimestamps();
Sébastien Blinc6f3a262017-07-28 16:12:24 -0400233}
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400234
Sébastien Blinc6f3a262017-07-28 16:12:24 -0400235setInterval(updateView, 60000);
236
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400237window.onresize = function(event) {
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400238 updateTimestamps();
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400239};
240
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400241function addBannedContact()
242{
243 window.prompt('UNBLOCK');
244}
245
246function addToConversations()
247{
248 window.prompt('ADD_TO_CONVERSATIONS');
249}
250
251function addBackButtonHoverProperty()
252{
253 if(hoverBackButtonAllowed) {
254 backButton.classList.add("non-action-button");
255 }
256}
257
258function placeCall()
259{
260 window.prompt('PLACE_CALL');
261}
262
263function placeAudioCall()
264{
265 window.prompt('PLACE_AUDIO_CALL');
266}
267
268function backToWelcomeView()
269{
270 backButton.classList.remove("non-action-button");
271 hoverBackButtonAllowed = false;
272 window.prompt('CLOSE_CHATVIEW');
273}
274
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400275function setDisplayLinks(display) {
276 displayLinksEnabled = display;
277}
aviau039001d2016-09-29 16:39:05 -0400278
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400279/**
280 * Transform a date to a string group like "1 hour ago".
281 */
282function formatDate(date) {
283 const seconds = Math.floor((new Date() - date) / 1000);
284 var interval = Math.floor(seconds / (3600 * 24));
285 if (interval > 5) {
286 return date.toLocaleDateString();
Sébastien Blin70dc0b72017-07-31 16:24:41 -0400287 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400288 if (interval > 1) {
289 return interval + ' days ago';
Sébastien Blinc6f3a262017-07-28 16:12:24 -0400290 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400291 if (interval === 1) {
292 return interval + ' day ago';
293 }
294 interval = Math.floor(seconds / 3600);
295 if (interval > 1) {
296 return interval + ' hours ago';
297 }
298 if (interval === 1) {
299 return interval + ' hour ago';
300 }
301 interval = Math.floor(seconds / 60);
302 if (interval > 1) {
303 return interval + ' minutes ago';
304 }
305 return 'just now';
306}
Sébastien Blinc6f3a262017-07-28 16:12:24 -0400307
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400308/**
309 * Send #sendMessage #message value
310 */
311 function sendMessage()
312 {
313 var message = messageBarInput.value;
314 if (message.length > 0) {
315 messageBarInput.value = '';
316 window.prompt('SEND:' + message);
AmarOkb4253242017-07-13 11:21:39 -0400317 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400318 }
AmarOkb4253242017-07-13 11:21:39 -0400319
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400320/**
321* Disable or enable textarea
322*/
323function disableSendMessage(isDisabled)
324{
325 messageBarInput.disabled = isDisabled;
326}
Sébastien Blinf4f90282017-10-03 14:07:16 -0400327
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400328function acceptInvitation()
329{
330 window.prompt('ACCEPT');
331}
Sébastien Blinf4f90282017-10-03 14:07:16 -0400332
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400333function refuseInvitation()
334{
335 window.prompt('REFUSE');
336}
Sébastien Blinf4f90282017-10-03 14:07:16 -0400337
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400338function blockConversation()
339{
340 window.prompt('BLOCK');
341}
Sébastien Blinf4f90282017-10-03 14:07:16 -0400342
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400343/**
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400344 * Clears all messages
345 */
346function clearMessages()
347{
348 messages.innerHTML = "";
349}
350
351/**
352 * Converts text to HTML
353 */
354function escapeHtml(html)
355{
356 var text = document.createTextNode(html);
357 var div = document.createElement('div');
358 div.appendChild(text);
359 return div.innerHTML;
360}
361
362
363/**
364 * Get the youtube video id from a URI
365 */
366function youtube_id(url) {
367 const regExp = /^.*(youtu\.be\/|v\/|\/u\/w|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
368 const match = url.match(regExp);
369 return (match && match[2].length == 11) ? match[2] : null;
370}
371
372/**
373 * Returns HTML message from the message text.
374 * Cleaned and linkified.
375 */
376function getMessageHtml(message_text)
377{
378 const escaped_message = escapeHtml(message_text);
379 var linkified_message = linkifyHtml(escaped_message, {});
380
381 const textPart = document.createElement('pre');
382 textPart.innerHTML = linkified_message;
383
384 return textPart.outerHTML;
385}
386
387/**
388 * Returns the message status, formatted for display
389 */
390function getMessageDeliveryStatusText(message_delivery_status)
391{
392 var formatted_delivery_status = message_delivery_status;
393
394 switch(message_delivery_status)
aviau039001d2016-09-29 16:39:05 -0400395 {
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400396 case "sending":
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400397 case "ongoing":
398 formatted_delivery_status = "Sending<svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><circle class='status_circle anim-first' cx='4' cy='12' r='1'/><circle class='status_circle anim-second' cx='8' cy='12' r='1'/><circle class='status_circle anim-third' cx='12' cy='12' r='1'/></svg>";
399 break;
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400400 case "failure":
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400401 formatted_delivery_status = "Failure <svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><path class='status-x x-first' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M4,4 L12,12'/><path class='status-x x-second' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M12,4 L4,12'/></svg>";
402 break;
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400403 case "sent":
404 case "finished":
405 case "unknown":
406 case "read":
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400407 formatted_delivery_status = "";
408 break;
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400409 default:
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400410 console.log("getMessageDeliveryStatusText: unknown delivery status: " + message_delivery_status);
411 break;
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400412 }
413
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400414 return formatted_delivery_status;
415}
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400416
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400417/**
418 * Returns the message date, formatted for display
419 */
420function getMessageTimestampText(message_timestamp, custom_format)
421{
422 const date = new Date(1000 * message_timestamp);
423 if(custom_format) {
424 return formatDate(date);
425 } else {
426 return date.toLocaleString();
427 }
428}
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400429
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400430/**
431 * Merge timestamps if they are from the same group (likes: "1 hour ago")
432 */
433function cleanPreviousTimestamps (baseNode, endIndex) {
434 // Remove previous elements with the same formatted timestamp
435 for (var c = endIndex - 1 ; c >= 0 ; --c) {
436 const child = messages.children[c];
437 if (child.className.indexOf('timestamp') !== -1) {
438 if (child.className === baseNode.className &&
439 child.innerHTML === baseNode.innerHTML) {
440 messages.removeChild(child);
441 } else {
442 break; // A different timestamp is met, we can stop here.
Sébastien Blin1e2dd352018-04-13 11:34:53 -0400443 }
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400444 }
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400445 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400446}
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400447
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400448/**
449 * Update timestamps
450 */
451function updateTimestamps() {
452 const timestamps = messages.querySelectorAll('.timestamp');
453 const is_image_out = messages.querySelector('.message_out .sender_image')
454 const is_image_in = messages.querySelector('.message_in .sender_image')
455 if (timestamps) {
456 for ( var c = 0 ; c < messages.children.length ; ++c) {
457 const child = messages.children[c];
458 if (child.className.indexOf('timestamp') !== -1) {
459 // Update timestamp
460 child.innerHTML = getMessageTimestampText(child.getAttribute('message_timestamp'), true)
Sébastien Blin82d0d2d2018-03-14 17:21:42 -0400461
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400462 var desktop_margin = '25%'
463 const height = document.body.clientHeight
464 const width = document.body.clientWidth
465 if (width <= 1920 || height <= 1080) {
466 desktop_margin = '15%'
Adrien Beraud8e25afb2017-04-19 01:38:57 +0200467 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400468 if (width <= 1000 || height <= 480) {
469 desktop_margin = '0px'
470 }
471 if (child.className.indexOf('timestamp_out') !== -1) {
472 const avatar_px = is_image_out ? (is_image_out.offsetHeight === avatar_size ? '60px' : '20px') : '20px'
473 child.style.paddingRight = `calc(${desktop_margin} + ${avatar_px})`
474 } else if (child.className.indexOf('timestamp_in') !== -1) {
475 const avatar_px = is_image_in ? (is_image_in.offsetHeight === avatar_size ? '60px' : '20px') : '20px'
476 child.style.paddingLeft = `calc(${desktop_margin} + ${avatar_px})`
477 }
478 // Remove previous elements with the same formatted timestamp
479 cleanPreviousTimestamps(child, c);
Adrien Beraud8e25afb2017-04-19 01:38:57 +0200480 }
Frédéric Guimont3e5f1b62016-11-22 11:41:38 -0500481 }
aviau039001d2016-09-29 16:39:05 -0400482 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400483}
aviau039001d2016-09-29 16:39:05 -0400484
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400485/**
486 * Convert a value in filesize
487 */
488 function humanFileSize(bytes) {
489 var thresh = 1024;
490 if(Math.abs(bytes) < thresh) {
491 return bytes + ' B';
492 }
493 var units = ['kB','MB','GB','TB','PB','EB','ZB','YB']
494 var u = -1;
495 do {
496 bytes /= thresh;
497 ++u;
498 } while(Math.abs(bytes) >= thresh && u < units.length - 1);
499 return bytes.toFixed(1)+' '+units[u];
500}
501
502/**
503 * Change the value of the progress bar
504 */
505function updateProgressBar(progress_bar, message_object, message_delivery_status) {
506 var delivery_status = message_object["delivery_status"];
507 if ("progress" in message_object && !isErrorStatus(delivery_status) && message_object["progress"] !== 100) {
508 var progress_percent = (100 * message_object["progress"] / message_object["totalSize"]);
509 if (progress_percent !== 100)
510 progress_bar.childNodes[0].setAttribute("style", "width: " + progress_percent + "%");
511 else
512 progress_bar.setAttribute("style", "display: none");
513 } else
514 progress_bar.setAttribute("style", "display: none");
515}
516
517/**
518 * Check if a status is an error status
519 */
520function isErrorStatus(status) {
521 return (status === 'failure'
522 || status === 'awaiting peer timeout'
523 || status === 'canceled'
524 || status === 'unjoinable peer');
525}
526
527/**
528 * Build a new file interaction
529 * @param message_id
530 */
531function fileInteraction(message_id) {
532 var message_wrapper = document.createElement('div')
533 message_wrapper.setAttribute('class', 'message_wrapper')
534
535 // An file interaction contains buttons at the left of the interaction
536 // for the status or accept/refuse
537 var left_buttons = document.createElement('div')
538 left_buttons.setAttribute('class', 'left_buttons')
539 message_wrapper.appendChild(left_buttons)
540 // Also contains a bold clickable text
541 var text_div = document.createElement('div')
542 text_div.setAttribute('class', 'text')
543 text_div.addEventListener('click', function (event) {
544 // ask ring to open the file
545 const filename = document.querySelector('#message_' + message_id + ' .full').innerText
546 window.prompt('OPEN_FILE:' + filename)
547 });
548 var full_div = document.createElement('div')
549 full_div.setAttribute('class', 'full')
550 full_div.style = 'visibility: hidden; display: none;'
551 var message_text = document.createElement('div')
552 message_text.setAttribute('class', 'filename')
553 // And some informations like size or error message.
554 var informations_div = document.createElement('div')
555 informations_div.setAttribute('class', 'informations')
556 text_div.appendChild(message_text)
557 text_div.appendChild(full_div)
558 text_div.appendChild(informations_div)
559 message_wrapper.appendChild(text_div)
560 // And finally, a progress bar
561 message_transfer_progress_bar = document.createElement('span')
562 message_transfer_progress_bar.setAttribute('class', 'message_progress_bar')
563 message_transfer_progress_completion = document.createElement('span')
564 message_transfer_progress_bar.appendChild(message_transfer_progress_completion)
565 message_wrapper.appendChild(message_transfer_progress_bar)
566 return message_wrapper
567}
568
569/**
570 * Update a file interaction (icons + filename + status + progress bar)
571 * @param message_div the message to update
572 * @param message_object new informations
573 */
574 function updateFileInteraction(message_div, message_object, forceTypeToFile = false) {
575 if (!message_div.querySelector('.informations')) return // media
576
577 var acceptSvg = '<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>',
578 refuseSvg = '<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
579 fileSvg = '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
580 warningSvg = '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>'
581 var message_delivery_status = message_object["delivery_status"]
582 var message_direction = message_object["direction"]
583 var message_id = message_object["id"]
584 var message_text = message_object['text']
585
586
587 if (isImage(message_text) && message_delivery_status === 'finished' && displayLinksEnabled && !forceTypeToFile) {
588 // Replace the old wrapper by the downloaded image
589 if (message_div.querySelector('.message_wrapper')) {
590 wrapper = message_div.querySelector('.message_wrapper')
591 wrapper.parentNode.removeChild(wrapper)
592 }
593 message_div.append(mediaInteraction(message_id, message_text))
594 message_div.querySelector('img').id = message_id
595 message_div.querySelector('img').msg_obj = message_object
596 message_div.querySelector('img').onerror = function() {
597 message_div = document.getElementById('#message_' + this.id);
598 if (message_div.querySelector('.message_wrapper')) {
599 wrapper = message_div.querySelector('.message_wrapper')
600 wrapper.parentNode.removeChild(wrapper)
601 }
602 message_div.append(fileInteraction(message_id))
603 updateFileInteraction(message_div, this.msg_obj, true)
604 }
605 return
606 }
607
608 // Set informations text
609 var informations_div = message_div.querySelector(".informations");
610 var informations_txt = getMessageTimestampText(message_object["timestamp"], true);
611 if (message_object["totalSize"] && message_object["progress"]) {
612 if (message_delivery_status === 'finished') {
613 informations_txt += " - " + humanFileSize(message_object["totalSize"]);
614 } else {
615 informations_txt += " - " + humanFileSize(message_object["progress"])
616 + " / " + humanFileSize(message_object["totalSize"]);
617 }
618 }
619 informations_txt += " - " + message_delivery_status;
620 informations_div.innerText = informations_txt;
621
622 // Update flat buttons
623 var left_buttons = message_div.querySelector(".left_buttons");
624 left_buttons.innerHTML = '';
625 if (message_delivery_status === 'awaiting peer' || message_delivery_status === 'awaiting host' || message_delivery_status.indexOf('ongoing') === 0) {
626 if (message_direction === 'in' && message_delivery_status.indexOf('ongoing') !== 0) {
627 // add buttons to accept or refuse a call.
628 var accept_button = document.createElement('div');
629 accept_button.innerHTML = acceptSvg;
630 accept_button.setAttribute("title", "Accept");
631 accept_button.setAttribute("class", "flat-button accept");
632 accept_button.onclick = function() {
633 window.prompt('ACCEPT_FILE:' + message_id);
634 }
635 left_buttons.appendChild(accept_button);
636 }
637 var refuse_button = document.createElement('div');
638 refuse_button.innerHTML = refuseSvg;
639 refuse_button.setAttribute("title", "Refuse");
640 refuse_button.setAttribute("class", "flat-button refuse");
641 refuse_button.onclick = function() {
642 window.prompt('REFUSE_FILE:' + message_id);
643 }
644 left_buttons.appendChild(refuse_button);
645 } else {
646 var status_button = document.createElement('div');
647 var statusFile = fileSvg;
648 if (isErrorStatus(message_delivery_status))
649 statusFile = warningSvg;
650 status_button.innerHTML = statusFile;
651 status_button.setAttribute("class", "flat-button");
652 left_buttons.appendChild(status_button);
653 }
654
655 message_div.querySelector('.full').innerText = message_text
656 message_div.querySelector('.filename').innerText = message_text.split('/').pop()
657 message_div.querySelector('.filename').innerText = message_text.split('/').pop()
658 updateProgressBar(message_div.querySelector('.message_progress_bar'), message_object);
659 updateProgressBar(message_div.querySelector('.message_progress_bar'), message_object);
660 }
661
662/**
663 * Return if a file is an image
664 * @param file
665 */
666function isImage(file) {
667 return file.toLowerCase().match(/\.(jpeg|jpg|gif|png)$/) !== null
668}
669
670/**
671 * Return if a file is a youtube video
672 * @param file
673 */
674function isVideo(file) {
675 const availableProtocols = ['http:', 'https:']
676 const videoHostname = ['youtube.com', 'www.youtube.com', 'youtu.be']
677 const urlParser = document.createElement('a')
678 urlParser.href = file
679 return (availableProtocols.includes(urlParser.protocol) && videoHostname.includes(urlParser.hostname))
680}
681
682/**
683 * Try to show an image or a video link (youtube for now)
684 * @param message_id
685 * @param link to show
686 * @param ytid if it's a youtube video
687 */
688function mediaInteraction(message_id, link, ytid, noerror) {
689 // TODO promise?
690 // Try to display images.
691 const media_wrapper = document.createElement('div')
692 media_wrapper.setAttribute('class', 'media_wrapper')
693 const linkElt = document.createElement('a')
694 linkElt.href = link
695 linkElt.style = 'text-decoration: none;'
696 const imageElt = document.createElement('img')
697 // Note, here, we don't check the size of the image.
698 // in the future, we can check the content-type and content-length with a request
699 // and maybe disable svg
700 if (noerror)
701 imageElt.setAttribute('onerror', 'this.style.display=\'none\'')
702 if (ytid) {
703 imageElt.src = `http://img.youtube.com/vi/${ytid}/0.jpg`
704 } else {
705 imageElt.src = link
706 }
707 linkElt.appendChild(imageElt)
708 if (ytid) {
709 const containerElt = document.createElement('div');
710 containerElt.setAttribute('class', 'containerVideo');
711 const playDiv = document.createElement('div')
712 playDiv.setAttribute('class', 'playVideo')
713 playDiv.innerHTML = '<svg fill="#ffffff" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">\
714 <path d="M8 5v14l11-7z"/>\
715 <path d="M0 0h24v24H0z" fill="none"/>\
716 </svg>'
717 linkElt.appendChild(playDiv)
718 containerElt.appendChild(linkElt)
719 media_wrapper.appendChild(containerElt)
720 } else {
721 media_wrapper.appendChild(linkElt)
722 }
723 return media_wrapper
724}
725
726/**
727 * Build a new text interaction
728 * @param message_id
729 * @param htmlText the DOM to show
730 */
731function textInteraction(message_id, htmlText) {
732 const message_wrapper = document.createElement('div')
733 message_wrapper.setAttribute('class', 'message_wrapper')
734 var message_text = document.createElement('span')
735 message_text.setAttribute('class', 'message_text')
736 message_text.innerHTML = htmlText
737 message_wrapper.appendChild(message_text)
738 // TODO STATUS
739 return message_wrapper
740}
741
742/**
743 * Update a text interaction (text)
744 * @param message_div the message to update
745 * @param delivery_status the status of the message
746 * @TODO retry button
747 */
748function updateTextInteraction(message_div, delivery_status) {
749 if (!message_div.querySelector('.message_text')) return // media
750 switch(delivery_status)
751 {
752 case "ongoing":
753 case "sending":
754 var sending_div = message_div.querySelector('.sending')
755 if (!sending_div) {
756 sending_div = document.createElement('div')
757 sending_div.setAttribute('class', 'sending')
758 sending_div.innerHTML = '<svg overflow="hidden" viewBox="0 -2 16 14" height="16px" width="16px"><circle class="status_circle anim-first" cx="4" cy="12" r="1"/><circle class="status_circle anim-second" cx="8" cy="12" r="1"/><circle class="status_circle anim-third" cx="12" cy="12" r="1"/></svg>'
759 // add sending animation to message
760 message_div.appendChild(sending_div)
Sébastien Blin05317a72018-02-21 11:09:16 -0500761 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400762 message_div.querySelector('.message_text').style = 'color: #888'
763 break
764 case "failure":
765 // change text color to red
766 message_div.querySelector('.message_wrapper').style = 'background-color: #f3a6a6'
767 message_div.querySelector('.message_text').style = 'color: #000'
768 var failure_div = message_div.querySelector('.failure')
769 if (!failure_div) {
770 failure_div = document.createElement('div')
771 failure_div.setAttribute('class', 'failure')
772 failure_div.innerHTML = '<svg overflow="visible" viewBox="0 -2 16 14" height="16px" width="16px"><path class="status-x x-first" stroke="#AA0000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" fill="none" d="M4,4 L12,12"/><path class="status-x x-second" stroke="#AA0000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" fill="none" d="M12,4 L4,12"/></svg>'
773 // add failure animation to message
774 message_div.appendChild(failure_div)
775 }
776 var sending = message_div.querySelector('.sending')
777 if (sending) sending.style.visibility = 'hidden'
778 break
779 case "sent":
780 case "finished":
781 case "unknown":
782 case "read":
783 // change text color to black
784 message_div.querySelector('.message_text').style = 'color: #000'
785 var sending = message_div.querySelector('.sending')
786 if (sending) sending.style.visibility = 'hidden'
787 break
788 default:
789 console.log("getMessageDeliveryStatusText: unknown delivery status: " + delivery_status)
790 break
791 }
792}
793
794/**
795 * Build a new interaction (call or contact)
796 * @param message_id
797 */
798function actionInteraction(message_id) {
799 var message_wrapper = document.createElement('div')
800 message_wrapper.setAttribute('class', 'message_wrapper')
801
802 // An file interaction contains buttons at the left of the interaction
803 // for the status or accept/refuse
804 var left_buttons = document.createElement('div')
805 left_buttons.setAttribute('class', 'left_buttons')
806 message_wrapper.appendChild(left_buttons)
807 // Also contains a bold clickable text
808 var text_div = document.createElement('div')
809 text_div.setAttribute('class', 'text')
810 message_wrapper.appendChild(text_div)
811 return message_wrapper
812}
813
814/**
815 * Update a call interaction (icon + text)
816 * @param message_div the message to update
817 * @param message_object new informations
818 */
819function updateCallInteraction(message_div, message_object) {
820 const outgoingCall = '<svg fill="#219d55" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/></svg>'
821 const callMissed = '<svg fill="#dc2719" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M19.59 7L12 14.59 6.41 9H11V7H3v8h2v-4.59l7 7 9-9z"/></svg>'
822 const outgoingMissed = '<svg fill="#dc2719" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M24 24H0V0h24v24z" id="a"/></defs><clipPath id="b"><use overflow="visible" xlink:href="#a"/></clipPath><path clip-path="url(#b)" d="M3 8.41l9 9 7-7V15h2V7h-8v2h4.59L12 14.59 4.41 7 3 8.41z"/></svg>'
823 const callReceived = '<svg fill="#219d55" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 5.41L18.59 4 7 15.59V9H5v10h10v-2H8.41z"/></svg>'
824
825 const message_text = message_object['text']
826 const message_direction = (message_text.toLowerCase().indexOf('incoming') !== -1) ? 'in' : 'out'
827 const missed = message_text.indexOf('Missed') !== -1
828
829 message_div.querySelector('.text').innerText = message_text.substring(2)
830
831 var left_buttons = message_div.querySelector('.left_buttons')
832 left_buttons.innerHTML = ''
833 var status_button = document.createElement('div')
834 var statusFile = ''
835 if (missed)
836 statusFile = (message_direction === 'in') ? callMissed : outgoingMissed
837 else
838 statusFile = (message_direction === 'in') ? callReceived : outgoingCall
839 status_button.innerHTML = statusFile
840 status_button.setAttribute('class', 'flat-button')
841 left_buttons.appendChild(status_button)
842}
843
844/**
845 * Update a contact interaction (icon + text)
846 * @param message_div the message to update
847 * @param message_object new informations
848 */
849function updateContactInteraction(message_div, message_object) {
850 const message_text = message_object['text']
851
852 message_div.querySelector('.text').innerText = message_text
853
854 var left_buttons = message_div.querySelector('.left_buttons')
855 left_buttons.innerHTML = ''
856 var status_button = document.createElement('div')
Hugo Lefeuvreedad8832018-05-14 16:36:06 -0400857 status_button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">\
858<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>\
859<path d="M0 0h24v24H0z" fill="none"/></svg>'
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400860 status_button.setAttribute('class', 'flat-button')
861 left_buttons.appendChild(status_button)
862}
863
864/**
865 * Add a message to the conversation.
866 * @param message_object to treat
867 * @param new_message if it's a new message or if we need to update
868 * @param insert_after if we want the message at the end or the top of the conversation
869 */
870function addOrUpdateMessage(message_object, new_message, insert_after = true) {
871 const message_id = message_object['id']
872 const message_type = message_object['type']
873 const message_text = message_object['text']
874 const message_direction = message_object['direction']
875 const message_timestamp = message_object['timestamp']
876 const delivery_status = message_object['delivery_status']
877 const message_sender_contact_method = message_object['sender_contact_method']
878
879 var message_div = document.querySelector('#message_' + message_id);
880 var type = ''
881 if (new_message) {
882 // Build new message
883 var classes = [
884 'message',
885 `message_${message_direction}`,
886 `message_type_${message_type}`
887 ]
888
889 message_div = document.createElement('div')
890 message_div.setAttribute('id', `message_${message_id}`)
891 message_div.setAttribute('class', classes.join(' '))
892
893 // Build message for each types.
894 // Add sender images if necessary (like if the interaction doesn't take the whole width)
895 const need_sender = (message_type === 'data_transfer' || message_type === 'text');
896 if (need_sender) {
897 var message_sender_image = document.createElement('span')
898 message_sender_image.setAttribute('class', `sender_image sender_image_${message_sender_contact_method}`)
899 message_div.appendChild(message_sender_image)
Sébastien Blin05317a72018-02-21 11:09:16 -0500900 }
901
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400902 // Build main content
903 if (message_type === 'data_transfer') {
904 if (isImage(message_text) && delivery_status === 'finished' && displayLinksEnabled) {
905 message_div.append(mediaInteraction(message_id, message_text, null, false))
906 message_div.querySelector('img').id = message_id
907 message_div.querySelector('img').msg_obj = message_object
908 message_div.querySelector('img').onerror = function() {
909 message_div = document.getElementById('#message_' + this.id);
910 if (message_div.querySelector('.message_wrapper')) {
911 wrapper = message_div.querySelector('.message_wrapper')
912 wrapper.parentNode.removeChild(wrapper)
913 }
914 message_div.append(fileInteraction(message_id))
915 updateFileInteraction(message_div, this.msg_obj, true)
Frédéric Guimont3e5f1b62016-11-22 11:41:38 -0500916 }
Sébastien Blinf4f90282017-10-03 14:07:16 -0400917 } else {
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400918 message_div.append(fileInteraction(message_id))
919 }
920 } else if (message_type === 'text') {
921 // TODO add the possibility to update messages (remove and rebuild)
922 const htmlText = getMessageHtml(message_text)
923 if (displayLinksEnabled) {
924 const parser = new DOMParser();
925 const DOMMsg = parser.parseFromString(htmlText, 'text/xml');
926 const links = DOMMsg.querySelectorAll('a');
927 if (DOMMsg.childNodes.length && links.length) {
928 var isTextToShow = (DOMMsg.childNodes[0].childNodes.length > 1)
929 const ytid = (isVideo(message_text))? youtube_id(message_text) : ''
930 if (!isTextToShow && (ytid || isImage(message_text))) {
931 type = 'media'
932 message_div.append(mediaInteraction(message_id, message_text, ytid))
933 }
934 }
935 }
936 if (type !== 'media') {
937 type = 'text'
938 message_div.append(textInteraction(message_id, htmlText))
939 }
940 } else if (message_type === 'call' || message_type === 'contact') {
941 message_div.append(actionInteraction(message_id))
942 } else {
943 const temp = document.createElement('div')
944 temp.innerText = message_type
945 message_div.appendChild(temp)
946 }
947
948 // Get timestamp to add
949 const formattedTimestamp = getMessageTimestampText(message_timestamp, true)
950 // Create the timestamp object
951 const date_elt = document.createElement('div')
952 date_elt.innerText = formattedTimestamp
953 if (message_type === 'call' || message_type === 'contact') {
954 timestamp_div_classes = [
955 'timestamp',
956 `timestamp_action`
957 ]
958 } else {
959 timestamp_div_classes = [
960 'timestamp',
961 `timestamp_${message_direction}`
962 ]
963 }
964 date_elt.setAttribute('class', timestamp_div_classes.join(' '))
965 date_elt.setAttribute('message_timestamp', message_timestamp)
966 // Remove last timestamp if it's the same<h6></h6>
967 if (messages.querySelectorAll('.timestamp'))
968 cleanPreviousTimestamps(date_elt, messages.children.length)
969
970 // Add message and the new timestamp
971 if (insert_after) {
972 if (message_type === 'call' || message_type === 'contact') {
973 message_div.querySelector('.message_wrapper').appendChild(date_elt)
974 messages.appendChild(message_div)
975 } else {
976 messages.appendChild(message_div)
977 messages.appendChild(date_elt)
978 }
979 } else {
980 if (message_type === 'call' || message_type === 'contact') {
981 message_div.querySelector('.message_wrapper').appendChild(date_elt)
982 messages.insertBefore(message_div, messages.firstChild)
983 } else {
984 messages.insertBefore(date_elt, messages.firstChild)
985 messages.insertBefore(message_div, messages.firstChild)
Sébastien Blinf4f90282017-10-03 14:07:16 -0400986 }
987 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400988 }
Sébastien Blinf4f90282017-10-03 14:07:16 -0400989
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -0400990 // Update informations if needed
991 if (message_type === 'data_transfer')
992 updateFileInteraction(message_div, message_object)
993 if (message_type === 'text' && message_direction === 'out')
994 // Modify sent status if necessary
995 updateTextInteraction(message_div, delivery_status)
996 if (message_type === 'call')
997 updateCallInteraction(message_div, message_object)
998 if (message_type === 'contact')
999 updateContactInteraction(message_div, message_object)
1000
1001 // Clean timestamps
1002 updateTimestamps();
1003}
1004
1005
1006/**
1007 * Add a message to the buffer
1008 */
1009function addMessage(message_object)
1010{
1011 var atEnd = messages.scrollTop >= messages.scrollHeight - messages.clientHeight - 5;
1012 addOrUpdateMessage(message_object, true);
1013 if (atEnd) {
1014 var startTime = Date.now(),
1015 durTime = 250.,
1016 scrollStartHeight = messages.scrollHeight,
1017 scrollStart = messages.scrollTop,
1018 scrollDiff = scrollStartHeight - messages.clientHeight - scrollStart;
1019
1020 function loop() {
1021 var time = Date.now() - startTime,
1022 scrollHeight = messages.scrollHeight,
1023 diff = messages.scrollTop - scrollStart; // If user scrolls up (diff > 0).
1024
1025 if (time >= durTime || scrollHeight != scrollStartHeight || diff < 0) {
1026 if (diff >= 0) { // User scrolled up, don't autoscroll.
1027 messages.scrollTop = scrollHeight;
1028 }
1029 return false;
1030 } else {
1031 messages.scrollTop = scrollStart + (scrollDiff * (time/durTime));
1032 raf(loop);
1033 }
1034 }
1035 raf(loop); // Start the animation loop
1036 }
1037}
1038
1039/**
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04001040 * Show the history in reverse order
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001041 */
1042function printHistoryPart () {
1043 if(historyBuffer.length == 0) {
1044 // nothing to print
1045 return;
1046 }
1047
1048 var previousScrollHeightMinusTop = messages.scrollHeight - messages.scrollTop;
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04001049
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001050 // Show 10 messages
1051 for (var i = 0; i < 10; ++i) {
1052 addOrUpdateMessage(historyBuffer[historyBuffer.length - 1 - historyBufferIndex], true, false);
1053 historyBufferIndex ++;
1054 if (historyBufferIndex === historyBuffer.length)
1055 break;
1056 }
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04001057
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001058 // Replace the scrollbar to the wanted position
1059 messages.scrollTop = messages.scrollHeight - previousScrollHeightMinusTop;
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04001060
1061 // Make a short pause to minimize ressources consumption
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001062 // show quickly the first hundred messages
1063 if (historyBufferIndex !== historyBuffer.length)
1064 setTimeout(printHistoryPart, (historyBufferIndex > 100) ? 100 : 1);
1065}
1066
1067/**
1068 * Show the whole history in the chatview.
1069 */
1070function printHistory(messages_array)
1071{
1072 historyBuffer = messages_array;
1073 historyBufferIndex = 0;
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04001074 setTimeout(printHistoryPart, 0);
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001075}
1076
1077/**
1078 * Updated a message that was previously added with addMessage
1079 */
1080function updateMessage(message_object)
1081{
1082 addOrUpdateMessage(message_object, false);
1083}
1084
1085/**
1086 * Sets the image for a given sender
1087 * set_sender_image object should contain the following keys:
1088 * - sender: the name of the sender
1089 * - sender_image: base64 png encoding of the sender image
1090 */
1091function setSenderImage(set_sender_image_object)
1092{
1093 var sender_contact_method = set_sender_image_object['sender_contact_method'],
1094 sender_image = set_sender_image_object['sender_image'],
1095 sender_image_id = "sender_image_" + sender_contact_method,
1096 currentSenderImage = document.getElementById("#" + sender_image_id), // Remove the currently set sender image
1097 style;
1098
1099 if (currentSenderImage) {
1100 currentSenderImage.parentNode.removeChild(currentSenderImage);
Guillaume Roguez5b137be2018-02-21 10:44:58 -05001101 }
1102
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001103 // Create a new style element
1104 style = document.createElement('style');
1105
1106 style.type = 'text/css';
1107 style.id = sender_image_id;
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04001108 style.innerHTML = '.' + sender_image_id + " {content: url(data:image/png;base64," + sender_image + ");height: 35px;width: 35px;}";
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001109 document.head.appendChild(style);
1110}
1111
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001112function clearSenderImages()
1113{
1114 var styles = document.head.querySelectorAll("style"),
Hugo Lefeuvreedad8832018-05-14 16:36:06 -04001115 i = styles.length;
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001116
1117 while (i--){
1118 document.head.removeChild(styles[i]);
aviau039001d2016-09-29 16:39:05 -04001119 }
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001120}
aviau039001d2016-09-29 16:39:05 -04001121
Hugo Lefeuvre65a41d62018-05-23 10:12:24 -04001122function sendFile()
1123{
1124 window.prompt('SEND_FILE');
1125}
aviau039001d2016-09-29 16:39:05 -04001126
1127</script>
aviau039001d2016-09-29 16:39:05 -04001128</html>