aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1 | <html> |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 2 | <!-- Empty head might be needed for setSenderImage --> |
| 3 | <head> |
Frederic Guimont | d8343e6 | 2016-11-01 23:31:25 -0400 | [diff] [blame] | 4 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 5 | <meta charset=“utf-8”> |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 6 | </head> |
| 7 | |
| 8 | <body> |
Sébastien Blin | f4f9028 | 2017-10-03 14:07:16 -0400 | [diff] [blame] | 9 | <div id="invitation"> |
| 10 | <div id="text"> |
| 11 | </div> |
| 12 | <div id="actions"> |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 13 | <div id="accept-btn" class="invitation-button button-green" onclick="ring.chatview.acceptInvitation()" >Accept</div> |
| 14 | <div id="refuse-btn" class="invitation-button button-red" onclick="ring.chatview.refuseInvitation()" >Refuse</div> |
| 15 | <div id="block-btn" class="invitation-button button-red" onclick="ring.chatview.blockConversation()" >Block</div> |
Sébastien Blin | f4f9028 | 2017-10-03 14:07:16 -0400 | [diff] [blame] | 16 | </div> |
| 17 | </div> |
Adrien Beraud | 8e25afb | 2017-04-19 01:38:57 +0200 | [diff] [blame] | 18 | <div id="container"> |
| 19 | <div id="messages"></div> |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 20 | |
| 21 | <div id="sendMessage"> |
Sébastien Blin | 55bff9d | 2017-10-03 15:15:23 -0400 | [diff] [blame] | 22 | <textarea id="message" autofocus placeholder="Message" onkeyup="grow_text_area()" rows="1" disabled="false"></textarea> |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 23 | <div class="msg-button" onclick="ring.chatview.sendMessage()" title="Send"> |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 24 | <svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"> |
| 25 | <path xmlns="http://www.w3.org/2000/svg" d="M12,11.874v4.357l7-6.69l-7-6.572v3.983c-8.775,0-11,9.732-11,9.732C3.484,12.296,7.237,11.874,12,11.874z"/> |
| 26 | </svg> |
| 27 | </div> |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 28 | <div class="msg-button" onclick="ring.chatview.sendFile()" title="Send File"> |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 29 | <svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"> |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 30 | <path d="M0 0h24v24H0z" fill="none" /> |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 31 | <path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/> |
| 32 | </svg> |
| 33 | </div> |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 34 | </div> |
| 35 | </form> |
Adrien Beraud | 8e25afb | 2017-04-19 01:38:57 +0200 | [diff] [blame] | 36 | </div> |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 37 | </body> |
| 38 | |
Frederic Guimont | d8343e6 | 2016-11-01 23:31:25 -0400 | [diff] [blame] | 39 | <!-- |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 40 | <script src="https://soapbox.github.io/linkifyjs/js/linkify/linkify.min.js"></script> |
| 41 | <script src="https://soapbox.github.io/linkifyjs/js/linkify/linkify-string.min.js"></script> |
Frederic Guimont | d8343e6 | 2016-11-01 23:31:25 -0400 | [diff] [blame] | 42 | <script src="https://soapbox.github.io/linkifyjs/js/linkify/linkify-html.min.js"></script> |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 43 | <link rel="stylesheet" type="text/css" href="chatview.css"> |
Frederic Guimont | d8343e6 | 2016-11-01 23:31:25 -0400 | [diff] [blame] | 44 | --> |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 45 | |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 46 | <script> |
AmarOk | 6286ad4 | 2017-07-14 12:11:08 -0400 | [diff] [blame] | 47 | |
| 48 | document.querySelector("#message").addEventListener("keydown", function (e) { |
| 49 | e = e || event; |
| 50 | var map = {}; |
| 51 | map[e.keyCode] = e.type == 'keydown'; |
| 52 | if (e.ctrlKey || e.shiftKey) { |
| 53 | return true; |
| 54 | } |
| 55 | if (map[13]) { |
| 56 | ring.chatview.sendMessage(); |
| 57 | e.preventDefault(); |
| 58 | } |
| 59 | return true; |
| 60 | }); |
| 61 | |
| 62 | function grow_text_area() { |
| 63 | var messages = document.querySelector('#messages'); |
| 64 | var is_at_bottom = messages.scrollTop === (messages.scrollHeight - messages.offsetHeight); |
| 65 | |
| 66 | var area = document.querySelector("#message"); |
| 67 | var old_height = area.style.height; |
| 68 | area.style.height = "auto"; |
| 69 | area.style.height = area.scrollHeight +"px"; |
| 70 | |
| 71 | if (is_at_bottom) { |
| 72 | messages.scrollTop = messages.scrollHeight; |
| 73 | } |
| 74 | } |
| 75 | |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 76 | /* |
| 77 | * Update timestamps messages |
| 78 | */ |
| 79 | function updateView() { |
| 80 | if (ring.chatview) ring.chatview.updateTimestamps(); |
| 81 | } |
| 82 | setInterval(updateView, 60000); |
| 83 | |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 84 | window.onresize = function(event) { |
| 85 | if (ring.chatview) ring.chatview.updateTimestamps(); |
| 86 | }; |
| 87 | |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 88 | var ring = {}; // ring namespace |
| 89 | |
| 90 | ring.chatview = (function(){ |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 91 | const avatar_size = 35; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 92 | var dev = {}; // ring.chatview.dev namespace |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 93 | var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame; |
| 94 | var messages = document.querySelector("#messages"); |
Sébastien Blin | 70dc0b7 | 2017-07-31 16:24:41 -0400 | [diff] [blame] | 95 | var displayLinksEnabled = false; |
Sébastien Blin | 05317a7 | 2018-02-21 11:09:16 -0500 | [diff] [blame] | 96 | var historyBufferIndex = 0; // When showing a large amount of interactions, this counter store the interaction's index to show |
| 97 | var historyBuffer = []; // Before showing a large amount of interactions, this array is used as a buffer. |
Sébastien Blin | 70dc0b7 | 2017-07-31 16:24:41 -0400 | [diff] [blame] | 98 | |
| 99 | function setDisplayLinks(display) { |
| 100 | displayLinksEnabled = display; |
| 101 | } |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 102 | |
| 103 | /** |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 104 | * Transform a date to a string group likes 1 hour ago. |
| 105 | */ |
| 106 | function formatDate(date) { |
| 107 | const seconds = Math.floor((new Date() - date) / 1000); |
| 108 | var interval = Math.floor(seconds / (3600 * 24)); |
| 109 | if (interval > 5) { |
Sébastien Blin | 69242ce | 2017-08-04 11:40:50 -0400 | [diff] [blame] | 110 | return date.toLocaleDateString(); |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 111 | } |
| 112 | if (interval > 1) { |
| 113 | return interval + ' days ago'; |
| 114 | } |
| 115 | if (interval === 1) { |
| 116 | return interval + ' day ago'; |
| 117 | } |
| 118 | interval = Math.floor(seconds / 3600); |
| 119 | if (interval > 1) { |
| 120 | return interval + ' hours ago'; |
| 121 | } |
| 122 | if (interval === 1) { |
| 123 | return interval + ' hour ago'; |
| 124 | } |
| 125 | interval = Math.floor(seconds / 60); |
| 126 | if (interval > 1) { |
| 127 | return interval + ' minutes ago'; |
| 128 | } |
| 129 | return 'just now'; |
| 130 | } |
| 131 | |
| 132 | /** |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 133 | * Send #sendMessage #message value |
| 134 | */ |
| 135 | function sendMessage() |
| 136 | { |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 137 | var input = document.querySelector("#sendMessage #message"); |
| 138 | var message = input.value; |
| 139 | if (message.length > 0) { |
| 140 | input.value = ''; |
Sébastien Blin | f4f9028 | 2017-10-03 14:07:16 -0400 | [diff] [blame] | 141 | window.prompt('SEND:' + message); |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 142 | } |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 143 | } |
| 144 | |
Sébastien Blin | f4f9028 | 2017-10-03 14:07:16 -0400 | [diff] [blame] | 145 | /** |
| 146 | * Disable or enable textarea |
| 147 | */ |
| 148 | function disableSendMessage(isDisabled) |
| 149 | { |
| 150 | var input = document.querySelector("#sendMessage #message"); |
| 151 | input.disabled = isDisabled; |
| 152 | } |
| 153 | |
| 154 | /** |
| 155 | * Accept an invite |
| 156 | */ |
| 157 | function acceptInvitation() |
| 158 | { |
| 159 | window.prompt('ACCEPT'); |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Refuse an invite |
| 164 | */ |
| 165 | function refuseInvitation() |
| 166 | { |
| 167 | window.prompt('REFUSE'); |
| 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Accept an invite |
| 172 | */ |
| 173 | function blockConversation() |
| 174 | { |
| 175 | window.prompt('BLOCK'); |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Change the content of the div invitation and show it |
| 180 | * @param contactAlias |
| 181 | */ |
| 182 | function showInvitation(contactAlias) { |
| 183 | const invitationText = document.querySelector('#text'); |
| 184 | const invitation = document.querySelector('#invitation'); |
| 185 | invitationText.innerHTML = '<h1>' + contactAlias + ' sends you an invitation</h1>' |
| 186 | + 'Do you want to add them to the conversations list?<br>' |
| 187 | + 'Note: you can automatically accept this invitation by sending a message.'; |
| 188 | invitation.style.visibility = 'visible'; |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Hide the content of invitation |
| 193 | * @param contactAlias |
| 194 | */ |
| 195 | function hideInvitation() { |
| 196 | const invitation = document.querySelector('#invitation'); |
| 197 | invitation.style.visibility = 'hidden'; |
| 198 | } |
| 199 | |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 200 | /** |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 201 | * Clears all messages |
| 202 | */ |
| 203 | function clearMessages() |
| 204 | { |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 205 | messages.innerHTML = ""; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Converts text to HTML |
| 210 | */ |
| 211 | function escapeHtml(html) |
| 212 | { |
| 213 | var text = document.createTextNode(html); |
| 214 | var div = document.createElement('div'); |
| 215 | div.appendChild(text); |
| 216 | return div.innerHTML; |
| 217 | } |
| 218 | |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 219 | |
Sébastien Blin | 23c09e0 | 2017-07-28 16:19:17 -0400 | [diff] [blame] | 220 | /** |
| 221 | * Get the youtube video id from a URI |
| 222 | */ |
| 223 | function youtube_id(url) { |
| 224 | const regExp = /^.*(youtu\.be\/|v\/|\/u\/w|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; |
| 225 | const match = url.match(regExp); |
| 226 | return (match && match[2].length == 11) ? match[2] : null; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 227 | } |
| 228 | |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 229 | /** |
| 230 | * Returns HTML message from the message text. |
| 231 | * Cleaned and linkified. |
| 232 | */ |
| 233 | function getMessageHtml(message_text) |
| 234 | { |
| 235 | const escaped_message = escapeHtml(message_text); |
| 236 | var linkified_message = linkifyHtml(escaped_message, {}); |
Sébastien Blin | 23c09e0 | 2017-07-28 16:19:17 -0400 | [diff] [blame] | 237 | |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 238 | const textPart = document.createElement('pre'); |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 239 | textPart.innerHTML = linkified_message; |
Sébastien Blin | 23c09e0 | 2017-07-28 16:19:17 -0400 | [diff] [blame] | 240 | |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 241 | return textPart.outerHTML; |
| 242 | } |
Sébastien Blin | 23c09e0 | 2017-07-28 16:19:17 -0400 | [diff] [blame] | 243 | |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 244 | /** |
| 245 | * Returns the message status, formatted for display |
| 246 | */ |
| 247 | function getMessageDeliveryStatusText(message_delivery_status) |
| 248 | { |
| 249 | var formatted_delivery_status = message_delivery_status; |
| 250 | |
| 251 | switch(message_delivery_status) |
| 252 | { |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 253 | case "sending": |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 254 | case "ongoing": |
| 255 | 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>"; |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 256 | break; |
| 257 | case "failure": |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 258 | 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>"; |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 259 | break; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 260 | case "sent": |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 261 | case "finished": |
| 262 | case "unknown": |
| 263 | case "read": |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 264 | formatted_delivery_status = ""; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 265 | break; |
| 266 | default: |
| 267 | console.log("getMessageDeliveryStatusText: unknown delivery status: " + message_delivery_status); |
| 268 | break; |
| 269 | } |
| 270 | |
| 271 | return formatted_delivery_status; |
| 272 | } |
| 273 | |
| 274 | /** |
| 275 | * Returns the message date, formatted for display |
| 276 | */ |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 277 | function getMessageTimestampText(message_timestamp, custom_format) |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 278 | { |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 279 | const date = new Date(1000 * message_timestamp); |
| 280 | if(custom_format) { |
| 281 | return formatDate(date); |
| 282 | } else { |
| 283 | return date.toLocaleString(); |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * Merge timestamps if they are from the same group (likes: "1 hour ago") |
| 289 | */ |
| 290 | function cleanPreviousTimestamps (baseNode, endIndex) { |
| 291 | // Remove previous elements with the same formatted timestamp |
| 292 | for (var c = endIndex - 1 ; c >= 0 ; --c) { |
| 293 | const child = messages.children[c]; |
| 294 | if (child.className.indexOf('timestamp') !== -1) { |
| 295 | if (child.className === baseNode.className && |
| 296 | child.innerHTML === baseNode.innerHTML) { |
| 297 | messages.removeChild(child); |
| 298 | } else { |
| 299 | break; // A different timestamp is met, we can stop here. |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | /** |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 306 | * Update timestamps |
| 307 | */ |
| 308 | function updateTimestamps() { |
| 309 | const timestamps = messages.querySelectorAll('.timestamp'); |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 310 | const is_image_out = messages.querySelector('.message_out .sender_image') |
| 311 | const is_image_in = messages.querySelector('.message_in .sender_image') |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 312 | if (timestamps) { |
| 313 | for ( var c = 0 ; c < messages.children.length ; ++c) { |
| 314 | const child = messages.children[c]; |
| 315 | if (child.className.indexOf('timestamp') !== -1) { |
| 316 | // Update timestamp |
| 317 | child.innerHTML = getMessageTimestampText(child.getAttribute('message_timestamp'), true) |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 318 | |
| 319 | var desktop_margin = '25%' |
| 320 | const height = document.body.clientHeight |
| 321 | const width = document.body.clientWidth |
| 322 | if (width <= 1920 || height <= 1080) { |
| 323 | desktop_margin = '15%' |
| 324 | } |
| 325 | if (width <= 1000 || height <= 480) { |
| 326 | desktop_margin = '0px' |
| 327 | } |
| 328 | if (child.className.indexOf('timestamp_out') !== -1) { |
| 329 | const avatar_px = is_image_out ? (is_image_out.offsetHeight === avatar_size ? '60px' : '20px') : '20px' |
| 330 | child.style.paddingRight = `calc(${desktop_margin} + ${avatar_px})` |
| 331 | } else if (child.className.indexOf('timestamp_in') !== -1) { |
| 332 | const avatar_px = is_image_in ? (is_image_in.offsetHeight === avatar_size ? '60px' : '20px') : '20px' |
| 333 | child.style.paddingLeft = `calc(${desktop_margin} + ${avatar_px})` |
| 334 | } |
Sébastien Blin | c6f3a26 | 2017-07-28 16:12:24 -0400 | [diff] [blame] | 335 | // Remove previous elements with the same formatted timestamp |
| 336 | cleanPreviousTimestamps(child, c); |
| 337 | } |
| 338 | } |
| 339 | } |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 340 | } |
| 341 | |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 342 | /** |
| 343 | * Convert a value in filesize |
| 344 | */ |
| 345 | function humanFileSize(bytes) { |
| 346 | var thresh = 1024; |
| 347 | if(Math.abs(bytes) < thresh) { |
| 348 | return bytes + ' B'; |
| 349 | } |
| 350 | var units = ['kB','MB','GB','TB','PB','EB','ZB','YB'] |
| 351 | var u = -1; |
| 352 | do { |
| 353 | bytes /= thresh; |
| 354 | ++u; |
| 355 | } while(Math.abs(bytes) >= thresh && u < units.length - 1); |
| 356 | return bytes.toFixed(1)+' '+units[u]; |
| 357 | } |
| 358 | |
| 359 | /** |
| 360 | * Change the value of the progress bar |
| 361 | */ |
| 362 | function updateProgressBar(progress_bar, message_object, message_delivery_status) { |
| 363 | var delivery_status = message_object["delivery_status"]; |
| 364 | if ("progress" in message_object && !isErrorStatus(delivery_status) && message_object["progress"] !== 100) { |
| 365 | var progress_percent = (100 * message_object["progress"] / message_object["totalSize"]); |
| 366 | if (progress_percent !== 100) |
| 367 | progress_bar.childNodes[0].setAttribute("style", "width: " + progress_percent + "%"); |
| 368 | else |
| 369 | progress_bar.setAttribute("style", "display: none"); |
| 370 | } else |
| 371 | progress_bar.setAttribute("style", "display: none"); |
| 372 | } |
| 373 | |
| 374 | /** |
| 375 | * Check if a status is an error status |
| 376 | */ |
| 377 | function isErrorStatus(status) { |
| 378 | return (status === 'failure' |
| 379 | || status === 'canceled' |
| 380 | || status === 'unjoinable peer'); |
| 381 | } |
| 382 | |
| 383 | /** |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 384 | * Build a new file interaction |
| 385 | * @param message_id |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 386 | */ |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 387 | function fileInteraction(message_id) { |
| 388 | var message_wrapper = document.createElement('div') |
| 389 | message_wrapper.setAttribute('class', 'message_wrapper') |
| 390 | |
| 391 | // An file interaction contains buttons at the left of the interaction |
| 392 | // for the status or accept/refuse |
| 393 | var left_buttons = document.createElement('div') |
| 394 | left_buttons.setAttribute('class', 'left_buttons') |
| 395 | message_wrapper.appendChild(left_buttons) |
| 396 | // Also contains a bold clickable text |
| 397 | var text_div = document.createElement('div') |
| 398 | text_div.setAttribute('class', 'text') |
| 399 | text_div.addEventListener('click', function (event) { |
| 400 | // ask ring to open the file |
| 401 | const filename = document.querySelector('#message_' + message_id + ' .full').innerText |
| 402 | window.prompt('OPEN_FILE:' + filename) |
| 403 | }); |
| 404 | var full_div = document.createElement('div') |
| 405 | full_div.setAttribute('class', 'full') |
| 406 | full_div.style = 'visibility: hidden; display: none;' |
| 407 | var message_text = document.createElement('div') |
| 408 | message_text.setAttribute('class', 'filename') |
| 409 | // And some informations like size or error message. |
| 410 | var informations_div = document.createElement('div') |
| 411 | informations_div.setAttribute('class', 'informations') |
| 412 | text_div.appendChild(message_text) |
| 413 | text_div.appendChild(full_div) |
| 414 | text_div.appendChild(informations_div) |
| 415 | message_wrapper.appendChild(text_div) |
| 416 | // And finally, a progress bar |
| 417 | message_transfer_progress_bar = document.createElement('span') |
| 418 | message_transfer_progress_bar.setAttribute('class', 'message_progress_bar') |
| 419 | message_transfer_progress_completion = document.createElement('span') |
| 420 | message_transfer_progress_bar.appendChild(message_transfer_progress_completion) |
| 421 | message_wrapper.appendChild(message_transfer_progress_bar) |
| 422 | return message_wrapper |
| 423 | } |
| 424 | |
| 425 | /** |
| 426 | * Update a file interaction (icons + filename + status + progress bar) |
| 427 | * @param message_div the message to update |
| 428 | * @param message_object new informations |
| 429 | */ |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 430 | function updateFileInteraction(message_div, message_object, forceTypeToFile = false) { |
| 431 | if (!message_div.querySelector('.informations')) return // media |
| 432 | |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 433 | 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>', |
| 434 | 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>', |
| 435 | 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>', |
| 436 | 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>' |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 437 | var message_delivery_status = message_object["delivery_status"] |
| 438 | var message_direction = message_object["direction"] |
| 439 | var message_id = message_object["id"] |
| 440 | var message_text = message_object['text'] |
| 441 | |
| 442 | |
| 443 | if (isImage(message_text) && message_delivery_status === 'finished' && displayLinksEnabled && !forceTypeToFile) { |
| 444 | // Replace the old wrapper by the downloaded image |
| 445 | if (message_div.querySelector('.message_wrapper')) { |
| 446 | wrapper = message_div.querySelector('.message_wrapper') |
| 447 | wrapper.parentNode.removeChild(wrapper) |
| 448 | } |
| 449 | message_div.append(mediaInteraction(message_id, message_text)) |
| 450 | message_div.querySelector('img').id = message_id |
| 451 | message_div.querySelector('img').msg_obj = message_object |
| 452 | message_div.querySelector('img').onerror = function() { |
| 453 | message_div = document.querySelector('#message_' + this.id); |
| 454 | if (message_div.querySelector('.message_wrapper')) { |
| 455 | wrapper = message_div.querySelector('.message_wrapper') |
| 456 | wrapper.parentNode.removeChild(wrapper) |
| 457 | } |
| 458 | message_div.append(fileInteraction(message_id)) |
| 459 | updateFileInteraction(message_div, this.msg_obj, true) |
| 460 | } |
| 461 | return |
| 462 | } |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 463 | |
| 464 | // Set informations text |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 465 | var informations_div = message_div.querySelector(".informations"); |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 466 | var informations_txt = getMessageTimestampText(message_object["timestamp"], true); |
| 467 | if (message_object["totalSize"] && message_object["progress"]) { |
| 468 | if (message_delivery_status === 'finished') { |
| 469 | informations_txt += " - " + humanFileSize(message_object["totalSize"]); |
| 470 | } else { |
| 471 | informations_txt += " - " + humanFileSize(message_object["progress"]) |
| 472 | + " / " + humanFileSize(message_object["totalSize"]); |
| 473 | } |
| 474 | } |
| 475 | informations_txt += " - " + message_delivery_status; |
| 476 | informations_div.innerText = informations_txt; |
| 477 | |
| 478 | // Update flat buttons |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 479 | var left_buttons = message_div.querySelector(".left_buttons"); |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 480 | left_buttons.innerHTML = ''; |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 481 | if (message_delivery_status.indexOf('awaiting') === 0 || message_delivery_status.indexOf('ongoing') === 0) { |
| 482 | if (message_direction === 'in' && message_delivery_status.indexOf('ongoing') !== 0) { |
Sébastien Blin | f21ffca | 2018-03-01 11:22:43 -0500 | [diff] [blame] | 483 | // add buttons to accept or refuse a call. |
| 484 | var accept_button = document.createElement('div'); |
| 485 | accept_button.innerHTML = acceptSvg; |
Sébastien Blin | f21ffca | 2018-03-01 11:22:43 -0500 | [diff] [blame] | 486 | accept_button.setAttribute("title", "Accept"); |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 487 | accept_button.setAttribute("class", "flat-button accept"); |
Sébastien Blin | f21ffca | 2018-03-01 11:22:43 -0500 | [diff] [blame] | 488 | accept_button.onclick = function() { |
| 489 | window.prompt('ACCEPT_FILE:' + message_id); |
| 490 | } |
| 491 | left_buttons.appendChild(accept_button); |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 492 | } |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 493 | var refuse_button = document.createElement('div'); |
| 494 | refuse_button.innerHTML = refuseSvg; |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 495 | refuse_button.setAttribute("title", "Refuse"); |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 496 | refuse_button.setAttribute("class", "flat-button refuse"); |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 497 | refuse_button.onclick = function() { |
| 498 | window.prompt('REFUSE_FILE:' + message_id); |
| 499 | } |
| 500 | left_buttons.appendChild(refuse_button); |
| 501 | } else { |
| 502 | var status_button = document.createElement('div'); |
| 503 | var statusFile = fileSvg; |
| 504 | if (isErrorStatus(message_delivery_status)) |
| 505 | statusFile = warningSvg; |
| 506 | status_button.innerHTML = statusFile; |
| 507 | status_button.setAttribute("class", "flat-button"); |
| 508 | left_buttons.appendChild(status_button); |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 509 | } |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 510 | |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 511 | message_div.querySelector('.full').innerText = message_text |
| 512 | message_div.querySelector('.filename').innerText = message_text.split('/').pop() |
| 513 | message_div.querySelector('.filename').innerText = message_text.split('/').pop() |
| 514 | updateProgressBar(message_div.querySelector('.message_progress_bar'), message_object); |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 515 | updateProgressBar(message_div.querySelector('.message_progress_bar'), message_object); |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 516 | } |
| 517 | |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 518 | /** |
| 519 | * Return if a file is an image |
| 520 | * @param file |
| 521 | */ |
| 522 | function isImage(file) { |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 523 | return file.toLowerCase().match(/\.(jpeg|jpg|gif|png)$/) !== null |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 524 | } |
| 525 | |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 526 | /** |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 527 | * Return if a file is a youtube video |
| 528 | * @param file |
| 529 | */ |
| 530 | function isVideo(file) { |
| 531 | const availableProtocols = ['http:', 'https:'] |
| 532 | const videoHostname = ['youtube.com', 'www.youtube.com', 'youtu.be'] |
| 533 | const urlParser = document.createElement('a') |
| 534 | urlParser.href = file |
| 535 | return (availableProtocols.includes(urlParser.protocol) && videoHostname.includes(urlParser.hostname)) |
| 536 | } |
| 537 | |
| 538 | /** |
| 539 | * Try to show an image or a video link (youtube for now) |
| 540 | * @param message_id |
| 541 | * @param link to show |
| 542 | * @param ytid if it's a youtube video |
| 543 | */ |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 544 | function mediaInteraction(message_id, link, ytid, noerror) { |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 545 | // TODO promise? |
| 546 | // Try to display images. |
| 547 | const media_wrapper = document.createElement('div') |
| 548 | media_wrapper.setAttribute('class', 'media_wrapper') |
| 549 | const linkElt = document.createElement('a') |
| 550 | linkElt.href = link |
| 551 | linkElt.style = 'text-decoration: none;' |
| 552 | const imageElt = document.createElement('img') |
| 553 | // Note, here, we don't check the size of the image. |
| 554 | // in the future, we can check the content-type and content-length with a request |
| 555 | // and maybe disable svg |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 556 | if (noerror) |
| 557 | imageElt.setAttribute('onerror', 'this.style.display=\'none\'') |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 558 | if (ytid) { |
| 559 | imageElt.src = `http://img.youtube.com/vi/${ytid}/0.jpg` |
| 560 | } else { |
| 561 | imageElt.src = link |
| 562 | } |
| 563 | linkElt.appendChild(imageElt) |
| 564 | if (ytid) { |
| 565 | const containerElt = document.createElement('div'); |
| 566 | containerElt.setAttribute('class', 'containerVideo'); |
| 567 | const playDiv = document.createElement('div') |
| 568 | playDiv.setAttribute('class', 'playVideo') |
| 569 | playDiv.innerHTML = '<svg fill="#ffffff" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">\ |
| 570 | <path d="M8 5v14l11-7z"/>\ |
| 571 | <path d="M0 0h24v24H0z" fill="none"/>\ |
| 572 | </svg>' |
| 573 | linkElt.appendChild(playDiv) |
| 574 | containerElt.appendChild(linkElt) |
| 575 | media_wrapper.appendChild(containerElt) |
| 576 | } else { |
| 577 | media_wrapper.appendChild(linkElt) |
| 578 | } |
| 579 | return media_wrapper |
| 580 | } |
| 581 | |
| 582 | /** |
| 583 | * Build a new text interaction |
| 584 | * @param message_id |
| 585 | * @param htmlText the DOM to show |
| 586 | */ |
| 587 | function textInteraction(message_id, htmlText) { |
| 588 | const message_wrapper = document.createElement('div') |
| 589 | message_wrapper.setAttribute('class', 'message_wrapper') |
| 590 | var message_text = document.createElement('span') |
| 591 | message_text.setAttribute('class', 'message_text') |
| 592 | message_text.innerHTML = htmlText |
| 593 | message_wrapper.appendChild(message_text) |
| 594 | // TODO STATUS |
| 595 | return message_wrapper |
| 596 | } |
| 597 | |
| 598 | /** |
| 599 | * Update a text interaction (text) |
| 600 | * @param message_div the message to update |
| 601 | * @param delivery_status the status of the message |
| 602 | * @TODO retry button |
| 603 | */ |
| 604 | function updateTextInteraction(message_div, delivery_status) { |
| 605 | if (!message_div.querySelector('.message_text')) return // media |
| 606 | switch(delivery_status) |
| 607 | { |
| 608 | case "ongoing": |
| 609 | case "sending": |
| 610 | var sending_div = message_div.querySelector('.sending') |
| 611 | if (!sending_div) { |
| 612 | sending_div = document.createElement('div') |
| 613 | sending_div.setAttribute('class', 'sending') |
| 614 | 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>' |
| 615 | // add sending animation to message |
| 616 | message_div.appendChild(sending_div) |
| 617 | } |
| 618 | message_div.querySelector('.message_text').style = 'color: #888' |
| 619 | break |
| 620 | case "failure": |
| 621 | // change text color to red |
| 622 | message_div.querySelector('.message_wrapper').style = 'background-color: #f3a6a6' |
| 623 | message_div.querySelector('.message_text').style = 'color: #000' |
| 624 | var failure_div = message_div.querySelector('.failure') |
| 625 | if (!failure_div) { |
| 626 | failure_div = document.createElement('div') |
| 627 | failure_div.setAttribute('class', 'failure') |
| 628 | 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>' |
| 629 | // add failure animation to message |
| 630 | message_div.appendChild(failure_div) |
| 631 | } |
| 632 | var sending = message_div.querySelector('.sending') |
| 633 | if (sending) sending.style.visibility = 'hidden' |
| 634 | break |
| 635 | case "sent": |
| 636 | case "finished": |
| 637 | case "unknown": |
| 638 | case "read": |
| 639 | // change text color to black |
| 640 | message_div.querySelector('.message_text').style = 'color: #000' |
| 641 | var sending = message_div.querySelector('.sending') |
| 642 | if (sending) sending.style.visibility = 'hidden' |
| 643 | break |
| 644 | default: |
| 645 | console.log("getMessageDeliveryStatusText: unknown delivery status: " + delivery_status) |
| 646 | break |
| 647 | } |
| 648 | } |
| 649 | |
| 650 | /** |
| 651 | * Build a new interaction (call or contact) |
| 652 | * @param message_id |
| 653 | */ |
| 654 | function actionInteraction(message_id) { |
| 655 | var message_wrapper = document.createElement('div') |
| 656 | message_wrapper.setAttribute('class', 'message_wrapper') |
| 657 | |
| 658 | // An file interaction contains buttons at the left of the interaction |
| 659 | // for the status or accept/refuse |
| 660 | var left_buttons = document.createElement('div') |
| 661 | left_buttons.setAttribute('class', 'left_buttons') |
| 662 | message_wrapper.appendChild(left_buttons) |
| 663 | // Also contains a bold clickable text |
| 664 | var text_div = document.createElement('div') |
| 665 | text_div.setAttribute('class', 'text') |
| 666 | message_wrapper.appendChild(text_div) |
| 667 | return message_wrapper |
| 668 | } |
| 669 | |
| 670 | /** |
| 671 | * Update a call interaction (icon + text) |
| 672 | * @param message_div the message to update |
| 673 | * @param message_object new informations |
| 674 | */ |
| 675 | function updateCallInteraction(message_div, message_object) { |
| 676 | 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>' |
| 677 | 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>' |
| 678 | 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>' |
| 679 | 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>' |
| 680 | |
| 681 | const message_text = message_object['text'] |
| 682 | const message_direction = (message_text.toLowerCase().indexOf('incoming') !== -1) ? 'in' : 'out' |
| 683 | const missed = message_text.indexOf('Missed') !== -1 |
| 684 | |
| 685 | message_div.querySelector('.text').innerText = message_text.substring(2) |
| 686 | |
| 687 | var left_buttons = message_div.querySelector('.left_buttons') |
| 688 | left_buttons.innerHTML = '' |
| 689 | var status_button = document.createElement('div') |
| 690 | var statusFile = '' |
| 691 | if (missed) |
| 692 | statusFile = (message_direction === 'in') ? callMissed : outgoingMissed |
| 693 | else |
| 694 | statusFile = (message_direction === 'in') ? callReceived : outgoingCall |
| 695 | status_button.innerHTML = statusFile |
| 696 | status_button.setAttribute('class', 'flat-button') |
| 697 | left_buttons.appendChild(status_button) |
| 698 | } |
| 699 | |
| 700 | /** |
| 701 | * Update a contact interaction (icon + text) |
| 702 | * @param message_div the message to update |
| 703 | * @param message_object new informations |
| 704 | */ |
| 705 | function updateContactInteraction(message_div, message_object) { |
| 706 | const message_text = message_object['text'] |
| 707 | |
| 708 | message_div.querySelector('.text').innerText = message_text |
| 709 | |
| 710 | var left_buttons = message_div.querySelector('.left_buttons') |
| 711 | left_buttons.innerHTML = '' |
| 712 | var status_button = document.createElement('div') |
| 713 | status_button.innerHTML = '<?xml version="1.0" ?><svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg">\ |
| 714 | <path d="M8.013766 23.107838l0.00465 -0.810382 0.033095 -0.133474c0.1495327 -0.603099 0.50107 -1.099452 1.1411917 -1.611311 0.1690725 -0.135195 0.5493165 -0.388522 0.7866284 -0.524069 1.4449359 -0.825313 3.6497829 -1.439907 5.5693029 -1.552424 0.268992 -0.01577 0.866138 -0.0085 1.131356 0.01373 1.873734 0.157215 3.884055 0.729268 5.262712 1.497544 1.031356 0.574739 1.70347 1.254543 1.937152 1.959316 0.101301 0.305518 0.100213 0.293393 0.105924 1.180132l0.0051 0.791314 -7.990878 0 -7.990878 0 0.00465 -0.810381z" fill="#000000"/>\ |
| 715 | <path d="M15.641818 15.906151c-0.512638 -0.04757 -0.970838 -0.177847 -1.424519 -0.40503 -0.534623 -0.267715 -0.972855 -0.622072 -1.344489 -1.087162 -0.500329 -0.626146 -0.797007 -1.385268 -0.858271 -2.196087 -0.01377 -0.182277 -0.0068 -0.568261 0.01341 -0.738308 0.137062 -1.155519 0.73576 -2.1602655 1.685808 -2.8291565 1.010233 -0.711264 2.333521 -0.90809 3.519055 -0.523424 0.511977 0.166119 0.9845 0.434474 1.39324 0.791249 0.103105 0.09 0.316993 0.304905 0.402137 0.404056 0.848222 0.9877635 1.16007 2.3144245 0.843044 3.5864705 -0.116312 0.466694 -0.339476 0.947863 -0.621858 1.340798 -0.695617 0.967955 -1.761735 1.568261 -2.95044 1.661324 -0.155481 0.01217 -0.501821 0.0097 -0.657113 -0.0047z" fill="#000000"/>\ |
| 716 | </svg>' |
| 717 | status_button.setAttribute('class', 'flat-button') |
| 718 | left_buttons.appendChild(status_button) |
| 719 | } |
| 720 | |
| 721 | /** |
| 722 | * Add a message to the conversation. |
| 723 | * @param message_object to treat |
| 724 | * @param new_message if it's a new message or if we need to update |
| 725 | * @param insert_after if we want the message at the end or the top of the conversation |
| 726 | */ |
| 727 | function addOrUpdateMessage(message_object, new_message, insert_after = true) { |
| 728 | const message_id = message_object['id'] |
| 729 | const message_type = message_object['type'] |
| 730 | const message_text = message_object['text'] |
| 731 | const message_direction = message_object['direction'] |
| 732 | const message_timestamp = message_object['timestamp'] |
| 733 | const delivery_status = message_object['delivery_status'] |
| 734 | const message_sender_contact_method = message_object['sender_contact_method'] |
| 735 | |
| 736 | var message_div = document.querySelector('#message_' + message_id); |
| 737 | var type = '' |
| 738 | if (new_message) { |
| 739 | // Build new message |
| 740 | var classes = [ |
| 741 | 'message', |
| 742 | `message_${message_direction}`, |
| 743 | `message_type_${message_type}` |
| 744 | ] |
| 745 | |
| 746 | message_div = document.createElement('div') |
| 747 | message_div.setAttribute('id', `message_${message_id}`) |
| 748 | message_div.setAttribute('class', classes.join(' ')) |
| 749 | |
| 750 | // Build message for each types. |
| 751 | // Add sender images if necessary (like if the interaction doesn't take the whole width) |
| 752 | const need_sender = (message_type === 'data_transfer' || message_type === 'text'); |
| 753 | if (need_sender) { |
| 754 | var message_sender_image = document.createElement('span') |
| 755 | message_sender_image.setAttribute('class', `sender_image sender_image_${message_sender_contact_method}`) |
| 756 | message_div.appendChild(message_sender_image) |
| 757 | } |
| 758 | |
| 759 | // Build main content |
| 760 | if (message_type === 'data_transfer') { |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 761 | if (isImage(message_text) && delivery_status === 'finished' && displayLinksEnabled) { |
| 762 | message_div.append(mediaInteraction(message_id, message_text, null, false)) |
| 763 | message_div.querySelector('img').id = message_id |
| 764 | message_div.querySelector('img').msg_obj = message_object |
| 765 | message_div.querySelector('img').onerror = function() { |
| 766 | message_div = document.querySelector('#message_' + this.id); |
| 767 | if (message_div.querySelector('.message_wrapper')) { |
| 768 | wrapper = message_div.querySelector('.message_wrapper') |
| 769 | wrapper.parentNode.removeChild(wrapper) |
| 770 | } |
| 771 | message_div.append(fileInteraction(message_id)) |
| 772 | updateFileInteraction(message_div, this.msg_obj, true) |
| 773 | } |
| 774 | } else { |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 775 | message_div.append(fileInteraction(message_id)) |
Sébastien Blin | 1e2dd35 | 2018-04-13 11:34:53 -0400 | [diff] [blame^] | 776 | } |
Sébastien Blin | 82d0d2d | 2018-03-14 17:21:42 -0400 | [diff] [blame] | 777 | } else if (message_type === 'text') { |
| 778 | // TODO add the possibility to update messages (remove and rebuild) |
| 779 | const htmlText = getMessageHtml(message_text) |
| 780 | if (displayLinksEnabled) { |
| 781 | const parser = new DOMParser(); |
| 782 | const DOMMsg = parser.parseFromString(htmlText, 'text/xml'); |
| 783 | const links = DOMMsg.querySelectorAll('a'); |
| 784 | if (DOMMsg.childNodes.length && links.length) { |
| 785 | var isTextToShow = (DOMMsg.childNodes[0].childNodes.length > 1) |
| 786 | const ytid = (isVideo(message_text))? youtube_id(message_text) : '' |
| 787 | if (!isTextToShow && (ytid || isImage(message_text))) { |
| 788 | type = 'media' |
| 789 | message_div.append(mediaInteraction(message_id, message_text, ytid)) |
| 790 | } |
| 791 | } |
| 792 | } |
| 793 | if (type !== 'media') { |
| 794 | type = 'text' |
| 795 | message_div.append(textInteraction(message_id, htmlText)) |
| 796 | } |
| 797 | } else if (message_type === 'call' || message_type === 'contact') { |
| 798 | message_div.append(actionInteraction(message_id)) |
| 799 | } else { |
| 800 | const temp = document.createElement('div') |
| 801 | temp.innerText = message_type |
| 802 | message_div.appendChild(temp) |
| 803 | } |
| 804 | |
| 805 | // Get timestamp to add |
| 806 | const formattedTimestamp = getMessageTimestampText(message_timestamp, true) |
| 807 | // Create the timestamp object |
| 808 | const date_elt = document.createElement('div') |
| 809 | date_elt.innerText = formattedTimestamp |
| 810 | if (message_type === 'call' || message_type === 'contact') { |
| 811 | timestamp_div_classes = [ |
| 812 | 'timestamp', |
| 813 | `timestamp_action` |
| 814 | ] |
| 815 | } else { |
| 816 | timestamp_div_classes = [ |
| 817 | 'timestamp', |
| 818 | `timestamp_${message_direction}` |
| 819 | ] |
| 820 | } |
| 821 | date_elt.setAttribute('class', timestamp_div_classes.join(' ')) |
| 822 | date_elt.setAttribute('message_timestamp', message_timestamp) |
| 823 | // Remove last timestamp if it's the same<h6></h6> |
| 824 | if (messages.querySelectorAll('.timestamp')) |
| 825 | cleanPreviousTimestamps(date_elt, messages.children.length) |
| 826 | |
| 827 | // Add message and the new timestamp |
| 828 | if (insert_after) { |
| 829 | if (message_type === 'call' || message_type === 'contact') { |
| 830 | message_div.querySelector('.message_wrapper').appendChild(date_elt) |
| 831 | messages.appendChild(message_div) |
| 832 | } else { |
| 833 | messages.appendChild(message_div) |
| 834 | messages.appendChild(date_elt) |
| 835 | } |
| 836 | } else { |
| 837 | if (message_type === 'call' || message_type === 'contact') { |
| 838 | message_div.querySelector('.message_wrapper').appendChild(date_elt) |
| 839 | messages.insertBefore(message_div, messages.firstChild) |
| 840 | } else { |
| 841 | messages.insertBefore(date_elt, messages.firstChild) |
| 842 | messages.insertBefore(message_div, messages.firstChild) |
| 843 | } |
| 844 | } |
| 845 | } |
| 846 | |
| 847 | // Update informations if needed |
| 848 | if (message_type === 'data_transfer') |
| 849 | updateFileInteraction(message_div, message_object) |
| 850 | if (message_type === 'text' && message_direction === 'out') |
| 851 | // Modify sent status if necessary |
| 852 | updateTextInteraction(message_div, delivery_status) |
| 853 | if (message_type === 'call') |
| 854 | updateCallInteraction(message_div, message_object) |
| 855 | if (message_type === 'contact') |
| 856 | updateContactInteraction(message_div, message_object) |
| 857 | |
| 858 | // Clean timestamps |
| 859 | updateTimestamps(); |
| 860 | } |
| 861 | |
| 862 | |
| 863 | /** |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 864 | * Add a message to the buffer |
| 865 | */ |
| 866 | function addMessage(message_object) |
| 867 | { |
Adrien Beraud | 8e25afb | 2017-04-19 01:38:57 +0200 | [diff] [blame] | 868 | var atEnd = messages.scrollTop >= messages.scrollHeight - messages.clientHeight - 5; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 869 | addOrUpdateMessage(message_object, true); |
Adrien Beraud | 8e25afb | 2017-04-19 01:38:57 +0200 | [diff] [blame] | 870 | if (atEnd) { |
| 871 | var startTime = Date.now(), |
| 872 | durTime = 250., |
| 873 | scrollStartHeight = messages.scrollHeight, |
| 874 | scrollStart = messages.scrollTop, |
| 875 | scrollDiff = scrollStartHeight - messages.clientHeight - scrollStart; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 876 | |
Adrien Beraud | 8e25afb | 2017-04-19 01:38:57 +0200 | [diff] [blame] | 877 | function loop() { |
| 878 | var time = Date.now() - startTime, |
| 879 | scrollHeight = messages.scrollHeight, |
| 880 | diff = messages.scrollTop - scrollStart; // If user scrolls up (diff > 0). |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 881 | |
Adrien Beraud | 8e25afb | 2017-04-19 01:38:57 +0200 | [diff] [blame] | 882 | if (time >= durTime || scrollHeight != scrollStartHeight || diff < 0) { |
| 883 | if (diff >= 0) { // User scrolled up, don't autoscroll. |
| 884 | messages.scrollTop = scrollHeight; |
| 885 | } |
| 886 | return false; |
| 887 | } else { |
| 888 | messages.scrollTop = scrollStart + (scrollDiff * (time/durTime)); |
| 889 | raf(loop); |
| 890 | } |
| 891 | } |
| 892 | raf(loop); // Start the animation loop |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 893 | } |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 894 | } |
| 895 | |
| 896 | /** |
Sébastien Blin | 05317a7 | 2018-02-21 11:09:16 -0500 | [diff] [blame] | 897 | * Show the history in reverse order and avoid the process to consumes a lot of ressources |
| 898 | */ |
| 899 | function printHistoryPart () { |
| 900 | var previousScrollHeightMinusTop = messages.scrollHeight - messages.scrollTop; |
| 901 | // Show 10 messages |
| 902 | for (var i = 0; i < 10; ++i) { |
| 903 | addOrUpdateMessage(historyBuffer[historyBuffer.length - 1 - historyBufferIndex], true, false); |
| 904 | historyBufferIndex ++; |
| 905 | if (historyBufferIndex === historyBuffer.length) |
| 906 | break; |
| 907 | } |
| 908 | // Replace the scrollbar to the wanted position |
| 909 | messages.scrollTop = messages.scrollHeight - previousScrollHeightMinusTop; |
| 910 | // Make a short pause to minimizes ressources consumption |
| 911 | // show quickly the first hundred messages |
| 912 | if (historyBufferIndex !== historyBuffer.length) |
| 913 | setTimeout(printHistoryPart, (historyBufferIndex > 100) ? 100 : 1); |
| 914 | } |
| 915 | |
| 916 | /** |
| 917 | * Show the whole history in the chatview. |
| 918 | */ |
| 919 | function printHistory(messages_array) |
| 920 | { |
| 921 | historyBuffer = messages_array; |
| 922 | historyBufferIndex = 0; |
| 923 | setTimeout(printHistoryPart, 1); |
| 924 | } |
| 925 | |
| 926 | /** |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 927 | * Updated a message that was previously added with addMessage |
| 928 | */ |
| 929 | function updateMessage(message_object) |
| 930 | { |
| 931 | addOrUpdateMessage(message_object, false); |
| 932 | } |
| 933 | |
| 934 | /** |
| 935 | * Sets the image for a given sender |
| 936 | * set_sender_image object should contain the following keys: |
| 937 | * - sender: the name of the sender |
| 938 | * - sender_image: base64 png encoding of the sender image |
| 939 | */ |
| 940 | function setSenderImage(set_sender_image_object) |
| 941 | { |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 942 | var sender_contact_method = set_sender_image_object['sender_contact_method'], |
| 943 | sender_image = set_sender_image_object['sender_image'], |
| 944 | sender_image_id = "sender_image_" + sender_contact_method, |
| 945 | currentSenderImage = document.querySelector("#" + sender_image_id), // Remove the currently set sender image |
| 946 | style; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 947 | |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 948 | if (currentSenderImage) { |
| 949 | currentSenderImage.parentNode.removeChild(currentSenderImage); |
| 950 | } |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 951 | |
| 952 | // Create a new style element |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 953 | style = document.createElement('style'); |
| 954 | |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 955 | style.type = 'text/css'; |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 956 | style.id = sender_image_id; |
Frederic Guimont | d8343e6 | 2016-11-01 23:31:25 -0400 | [diff] [blame] | 957 | style.innerHTML = '.' + sender_image_id + " { \n content: url(data:image/png;base64," + sender_image + "); \n height: 35px; \n width: 35px; \n }"; |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 958 | document.head.appendChild(style); |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 959 | } |
| 960 | |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 961 | /** |
| 962 | * Change the send icon |
| 963 | */ |
| 964 | function setSendIcon(source) |
| 965 | { |
| 966 | var sendBtn = document.querySelector("#sendicon"); |
| 967 | sendBtn.src = "data:image/png;base64," + source; |
| 968 | } |
| 969 | |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 970 | function clearSenderImages() |
| 971 | { |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 972 | var styles = document.head.querySelectorAll("style"), |
| 973 | i = styles.length; |
| 974 | |
| 975 | while (i--){ |
| 976 | document.head.removeChild(styles[i]); |
| 977 | } |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 978 | } |
| 979 | |
Sébastien Blin | f4f9028 | 2017-10-03 14:07:16 -0400 | [diff] [blame] | 980 | function setTemporary(temporary) |
| 981 | { |
| 982 | var area = document.querySelector("#message"); |
| 983 | var sendBtn = document.querySelector("#sendBtn"); |
| 984 | if (temporary) { |
| 985 | area.placeholder = "Note: an interaction will create a new contact."; |
| 986 | sendBtn.innerHTML = "<svg viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\">\ |
| 987 | <path d=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"/>\ |
| 988 | <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\ |
| 989 | </svg>"; |
| 990 | } else { |
| 991 | area.placeholder = "Message"; |
| 992 | sendBtn.innerHTML = "<svg viewBox=\"0 0 30 30\" xmlns=\"http://www.w3.org/2000/svg\">\ |
| 993 | <path xmlns=\"http://www.w3.org/2000/svg\"\ d=\"M12,11.874v4.357l7-6.69l-7-6.572v3.983c-8.775,0-11,9.732-11,9.732C3.484,12.296,7.237,11.874,12,11.874z\"/>\ |
| 994 | </svg>"; |
| 995 | } |
| 996 | } |
| 997 | |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 998 | function sendFile() |
| 999 | { |
| 1000 | window.prompt('SEND_FILE'); |
| 1001 | } |
| 1002 | |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1003 | return { |
| 1004 | addMessage: addMessage, |
Sébastien Blin | 05317a7 | 2018-02-21 11:09:16 -0500 | [diff] [blame] | 1005 | printHistory: printHistory, |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1006 | updateMessage: updateMessage, |
| 1007 | setSenderImage: setSenderImage, |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 1008 | setSendIcon: setSendIcon, |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1009 | dev: dev, |
| 1010 | clearMessages: clearMessages, |
| 1011 | clearSenderImages: clearSenderImages, |
AmarOk | b425324 | 2017-07-13 11:21:39 -0400 | [diff] [blame] | 1012 | sendMessage: sendMessage, |
Sébastien Blin | 70dc0b7 | 2017-07-31 16:24:41 -0400 | [diff] [blame] | 1013 | setDisplayLinks: setDisplayLinks, |
Sébastien Blin | f4f9028 | 2017-10-03 14:07:16 -0400 | [diff] [blame] | 1014 | setTemporary: setTemporary, |
| 1015 | acceptInvitation: acceptInvitation, |
| 1016 | refuseInvitation: refuseInvitation, |
| 1017 | blockConversation: blockConversation, |
| 1018 | showInvitation: showInvitation, |
| 1019 | hideInvitation: hideInvitation, |
| 1020 | disableSendMessage: disableSendMessage, |
Guillaume Roguez | 5b137be | 2018-02-21 10:44:58 -0500 | [diff] [blame] | 1021 | updateTimestamps: updateTimestamps, |
| 1022 | sendFile: sendFile, |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1023 | } |
| 1024 | |
| 1025 | })(); |
| 1026 | |
| 1027 | /* DEV functions */ |
| 1028 | ring.chatview.dev = (function(){ |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1029 | /** |
| 1030 | * Fills the backlog with bogus messages |
| 1031 | */ |
| 1032 | function fillMessages() |
| 1033 | { |
| 1034 | ring.chatview.clearMessages(); |
| 1035 | |
Frédéric Guimont | 3e5f1b6 | 2016-11-22 11:41:38 -0500 | [diff] [blame] | 1036 | profile_picture = "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"; |
| 1037 | |
| 1038 | profile_picture = "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"; |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1039 | |
| 1040 | // Set the profile pictures |
| 1041 | ring.chatview.setSenderImage( |
| 1042 | { |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1043 | "sender_contact_method": "self", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1044 | "sender_image": profile_picture, |
| 1045 | } |
| 1046 | ); |
| 1047 | |
| 1048 | ring.chatview.setSenderImage( |
| 1049 | { |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1050 | "sender_contact_method": "0x3345", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1051 | "sender_image": profile_picture, |
| 1052 | } |
| 1053 | ) |
| 1054 | |
| 1055 | |
| 1056 | // Create messages |
| 1057 | ring.chatview.addMessage( |
| 1058 | { |
| 1059 | "id": 0, |
| 1060 | "text": "Hello! This message's status will be updated to sent.", |
| 1061 | "sender": "Me", |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1062 | "sender_contact_method": "self", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1063 | "timestamp": 1475275399, |
| 1064 | "direction": "out", |
| 1065 | "delivery_status": "sending", |
| 1066 | } |
| 1067 | ); |
| 1068 | |
| 1069 | ring.chatview.updateMessage( |
| 1070 | { |
| 1071 | "id": 0, |
| 1072 | "text": "Hello! This message's status will be updated to sent.", |
| 1073 | "sender": "Me", |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1074 | "sender_contact_method": "self", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1075 | "timestamp": 1475275399, |
| 1076 | "direction": "out", |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 1077 | "delivery_status": "sending", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1078 | } |
| 1079 | ); |
| 1080 | |
| 1081 | ring.chatview.addMessage( |
| 1082 | { |
| 1083 | "id": 1, |
| 1084 | "text": "Hey! This message's status is 'Unknown', we shouldn't display that to the user.", |
| 1085 | "sender": "Other Guy", |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1086 | "sender_contact_method": "0x3345", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1087 | "timestamp": 1475275399, |
| 1088 | "direction": "in", |
| 1089 | "delivery_status": "unknown", |
| 1090 | } |
| 1091 | ); |
| 1092 | |
| 1093 | ring.chatview.addMessage( |
| 1094 | { |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 1095 | "id": 1, |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1096 | "text": "This is the second message in a row from me. Don't bother displaying my profile picture twice.", |
| 1097 | "sender": "Other Guy", |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1098 | "sender_contact_method": "0x3345", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1099 | "timestamp": 1475275399, |
| 1100 | "direction": "in", |
| 1101 | "delivery_status": "unknown", |
| 1102 | } |
| 1103 | ); |
| 1104 | |
| 1105 | ring.chatview.addMessage( |
| 1106 | { |
| 1107 | "id": 0, |
| 1108 | "text": "Now its my turn to speak!", |
| 1109 | "sender": "Me", |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1110 | "sender_contact_method": "self", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1111 | "timestamp": 1475275399, |
| 1112 | "direction": "out", |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 1113 | "delivery_status": "sent", |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1114 | } |
| 1115 | ); |
| 1116 | |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 1117 | ring.chatview.addMessage( |
| 1118 | { |
| 1119 | "id": 5, |
| 1120 | "text": "Hello! This message's status will be updated to sent.", |
| 1121 | "sender": "Me", |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1122 | "sender_contact_method": "self", |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 1123 | "timestamp": 1475275399, |
| 1124 | "direction": "out", |
| 1125 | "delivery_status": "sending", |
| 1126 | } |
| 1127 | ); |
| 1128 | |
| 1129 | |
| 1130 | ring.chatview.updateMessage( |
| 1131 | { |
| 1132 | "id": 5, |
| 1133 | "text": "Hello! This message's status will be updated to sent.", |
| 1134 | "sender": "Me", |
aviau | e52afa2 | 2016-11-08 13:27:31 -0500 | [diff] [blame] | 1135 | "sender_contact_method": "self", |
Frédéric Guimont | 13778a6 | 2016-11-02 21:21:21 -0400 | [diff] [blame] | 1136 | "timestamp": 1475275399, |
| 1137 | "direction": "out", |
| 1138 | "delivery_status": "failure", |
| 1139 | } |
| 1140 | ); |
aviau | 039001d | 2016-09-29 16:39:05 -0400 | [diff] [blame] | 1141 | |
| 1142 | } |
| 1143 | |
| 1144 | return { |
| 1145 | fillMessages: fillMessages, |
| 1146 | } |
| 1147 | |
| 1148 | })(); |
| 1149 | |
| 1150 | </script> |
| 1151 | |
| 1152 | </html> |