chatview: major cleanup

Despite of the recent optimization commits, the chatview is still
affected by a significant number of performance issues. This commit
tries to address some of them:

* Use getElementById instead of querySelector, when possible.
* Use getElementByClassName instead of querySelector, when possible.

(querySelector is in average 2x slower than getElementById or
getElementByClassName!)

In this commit we also address the following bugs:

* printHistoryPart: it doesn't make any sense to call
  addOrUpdateMessage if we are at the end of the buffer.
* the bottom padding of the body isn't updated with the size of
  the #message textarea, leading to an annoying overlap when writing
  large messages.
* selection highlighting in the chatview is crappy. The user should
  not be able to highlight the background because it doesn't make
  any sense. Only meaningful elements should be highlightable.
* The first time a message fails to be sent, the cross 'X' icon
  is displayed with a very bad looking shift to the right. This is
  because the 'sending' icon is still displayed, with visibility
  hidden.
* Many variables were not declared / using some broken, ancient
  JavaScript Sith magic we don't want to mess with. In order to make
  sure we are not going to rely on such mechanisms anymore, add
  "use strict" stanza.

In addition to that we also fix a _very_ large number of style issues
(semicolons, indentation, comments) and define a clear style policy
for the JS code.

A code audit revealed several small bugs which we are not going to
address in this commit. Add FIXME comments.

Also, remove some useless debug warnings from the GTK side chatview
code.

Change-Id: If6b605868ba6b0b9623ae01c5293064211b58327
Reviewed-by: Sebastien Blin <sebastien.blin@savoirfairelinux.com>
diff --git a/web/chatview.html b/web/chatview.html
index f4df001..cd88dbf 100644
--- a/web/chatview.html
+++ b/web/chatview.html
@@ -71,7 +71,7 @@
                 <path d="M0 0h24v24H0z" fill="none"/>
             </svg>
         </div>
-        <textarea id="message" autofocus placeholder="Type a message" onkeyup="grow_text_area()" rows="1" disabled="false"></textarea>
+        <textarea id="message" autofocus placeholder="Type a message" onkeyup="grow_text_area()" onkeydown="process_messagebar_keydown()" rows="1" disabled="false"></textarea>
         <div class="nav-button action-button" onclick="sendMessage()" title="Send">
             <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
                 <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
@@ -83,335 +83,420 @@
 </body>
 
 <script>
+"use strict"
 
 /* Constants used at several places*/
-const messageBarPlaceHolder = "Type a message";
-const avatar_size = 35;
-var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
-var displayLinksEnabled = false;
-var historyBufferIndex = 0; // When showing a large amount of interactions, this counter store the interaction's index to show
-var historyBuffer = []; // Before showing a large amount of interactions, this array is used as a buffer.
-var hoverBackButtonAllowed = true;
+const messageBarPlaceHolder = "Type a message"
+const avatar_size = 35
+var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame
+var displayLinksEnabled = false
+var historyBufferIndex = 0 // When showing a large amount of interactions, this counter store the interaction's index to show
+var historyBuffer = [] // Before showing a large amount of interactions, this array is used as a buffer.
+var hoverBackButtonAllowed = true
 
 /* We retrieve refs to the most used navbar and message bar elements for efficiency purposes */
 /* NOTE: always use getElementById when possible, way more efficient */
-const backButton        = document.getElementById("backButton");
-const aliasField        = document.getElementById("nav-contactid-alias");
-const bestIdField       = document.getElementById("nav-contactid-bestId");
-const idField           = document.getElementById("nav-contactid");
-const messageBar        = document.getElementById('sendMessage');
-const messageBarInput   = document.getElementById("message");
-const messages          = document.getElementById("messages");
-const addToConvButton   = document.getElementById("addToConversationsButton");
-const invitation        = document.getElementById("invitation");
-const invitationText    = document.getElementById('text');
-const callButtons       = document.getElementById("callButtons");
-const addBannedContactButton = document.getElementById("addBannedContactButton");
+const backButton        = document.getElementById("backButton")
+const aliasField        = document.getElementById("nav-contactid-alias")
+const bestIdField       = document.getElementById("nav-contactid-bestId")
+const idField           = document.getElementById("nav-contactid")
+const messageBar        = document.getElementById("sendMessage")
+const messageBarInput   = document.getElementById("message")
+const messages          = document.getElementById("messages")
+const addToConvButton   = document.getElementById("addToConversationsButton")
+const invitation        = document.getElementById("invitation")
+const navbar            = document.getElementById("navbar")
+const invitationText    = document.getElementById("text")
 
 /* States: allows us to avoid re-doing something if it isn't meaningful */
-var isBanned = false;
-var isTemporary = false;
-var hasInvitation = false;
+var isBanned = false
+var isTemporary = false
+var hasInvitation = false
 
-messageBarInput.addEventListener("keydown", function (e) {
-    e = e || event;
-    var map = {};
-    map[e.keyCode] = e.type == 'keydown';
-    if (e.ctrlKey || e.shiftKey) {
-      return true;
-    }
-    if (map[13]) {
-      sendMessage();
-      e.preventDefault();
-    }
-    return true;
-});
-
-/* Update general view info */
+/**
+ * Update common frame between conversations.
+ *
+ * Whenever the current conversation is switched, information from the navbar
+ * and message bar have to be updated to match new contact. This function
+ * handles most of the required work (except the showing/hiding the invitation,
+ * which is handled by showInvitation()).
+ *
+ * @param banned 	whether contact is banned or not
+ * @param temporary	whether contact is temporary or not
+ * @param alias
+ * @param bestId
+ */
+/* exported update_chatview_frame */
 function update_chatview_frame(banned, temporary, alias, bestid) {
-  navbar.style.display = "none";
+    navbar.style.display = "none"
 
-  hoverBackButtonAllowed = true;
+    hoverBackButtonAllowed = true
 
-  aliasField.innerHTML = (alias ? alias : bestid);
+    aliasField.innerHTML = (alias ? alias : bestid)
 
-  if(alias) {
-      bestIdField.innerHTML = bestid;
-      idField.classList.remove("oneEntry");
-  } else {
-      idField.classList.add("oneEntry");
-  }
+    if(alias) {
+        bestIdField.innerHTML = bestid
+        idField.classList.remove("oneEntry")
+    } else {
+        idField.classList.add("oneEntry")
+    }
 
-  if (isBanned !== banned) {
-      isBanned = banned;
-      hideMessageBar(banned);
+    if (isBanned !== banned) {
+        isBanned = banned
+        hideMessageBar(banned)
 
-      if(banned) {
-          // contact is banned. update navbar and states
-          navbar.classList.add('onBannedState');
-      } else {
-          navbar.classList.remove('onBannedState');
-      }
-  } else if (isTemporary !== temporary) {
-      isTemporary = temporary;
-      if (temporary) {
-          addToConvButton.style.display = 'flex';
-          messageBarInput.placeholder = "Note: an interaction will create a new contact.";
-      } else {
-          addToConvButton.style.display = '';
-          messageBarInput.placeholder = messageBarPlaceHolder;
-      }
-  }
+        if(banned) {
+            // contact is banned. update navbar and states
+            navbar.classList.add("onBannedState")
+        } else {
+            navbar.classList.remove("onBannedState")
+        }
+    } else if (isTemporary !== temporary) {
+        isTemporary = temporary
+        if (temporary) {
+            addToConvButton.style.display = "flex"
+            messageBarInput.placeholder = "Note: an interaction will create a new contact."
+        } else {
+            addToConvButton.style.display = ""
+            messageBarInput.placeholder = messageBarPlaceHolder
+        }
+    }
 
-  navbar.style.display = "";
+    navbar.style.display = ""
 }
 
 /**
- * Hide or show invitation. Invitation is hidden if no contactAlias/invalid alias is passed.
+ * Hide or show invitation.
+ *
+ * Invitation is hidden if no contactAlias/invalid alias is passed.
  * Otherwise, invitation div is updated.
- * @param  contactAlias
+ *
+ * @param contactAlias
  */
+/* exported showInvitation */
 function showInvitation(contactAlias) {
-  if (!contactAlias) {
-      if (hasInvitation) {
-          hasInvitation = false;
-          invitation.style.visibility = '';
-      }
-  } else {
-      hasInvitation = true;
-      invitationText.innerHTML = '<h1>' + contactAlias + ' sends you an invitation</h1>'
-      + 'Do you want to add them to the conversations list?<br>'
-      + 'Note: you can automatically accept this invitation by sending a message.';
-      invitation.style.visibility = 'visible';
-  }
+    if (!contactAlias) {
+        if (hasInvitation) {
+            hasInvitation = false
+            invitation.style.visibility = ""
+        }
+    } else {
+        hasInvitation = true
+        invitationText.innerHTML = "<h1>" + contactAlias + " sends you an invitation</h1>"
+      + "Do you want to add them to the conversations list?<br>"
+      + "Note: you can automatically accept this invitation by sending a message."
+        invitation.style.visibility = "visible"
+    }
 }
 
 /**
  * Hide or show navbar, and update body top padding accordingly.
+ *
+ * @param isVisible whether navbar should be displayed or not
  */
+/* exported displayNavbar */
 function displayNavbar(isVisible)
 {
     if (isVisible) {
-        navbar.classList.remove("hiddenState");
-        document.documentElement.style.setProperty('--navbar-size', undefined);
+        navbar.classList.remove("hiddenState")
+        document.documentElement.style.setProperty("--navbar-size", undefined)
     } else {
-        navbar.classList.add("hiddenState");
-        document.documentElement.style.setProperty('--navbar-size', '0');
+        navbar.classList.add("hiddenState")
+        document.documentElement.style.setProperty("--navbar-size", "0")
     }
 }
 
 /**
  * Hide or show message bar, and update body bottom padding accordingly.
+ *
+ * @param isHidden whether message bar should be displayed or not
  */
+/* exported hideMessageBar */
 function hideMessageBar(isHidden) {
     if (isHidden) {
-        messageBar.classList.add("hiddenState");
-        document.documentElement.style.setProperty('--messagebar-size', '0');
+        messageBar.classList.add("hiddenState")
+        document.body.style.setProperty("--messagebar-size", "0")
     } else {
-        messageBar.classList.remove("hiddenState");
-        document.documentElement.style.removeProperty('--messagebar-size');
+        messageBar.classList.remove("hiddenState")
+        document.body.style.removeProperty("--messagebar-size")
     }
 }
 
+/* exported setDisplayLinks */
+function setDisplayLinks(display) {
+    displayLinksEnabled = display
+}
+
+/**
+ * This event handler dynamically resizes the message bar depending on the amount of
+ * text entered, while adjusting the body paddings so that that the message bar doesn't
+ * overlap messages when it grows.
+ */
+/* exported grow_text_area */
 function grow_text_area() {
-  var is_at_bottom = messages.scrollTop === (messages.scrollHeight - messages.offsetHeight);
+    var is_at_bottom = messages.scrollTop === (messages.scrollHeight - messages.offsetHeight)
 
-  var old_height = messageBarInput.style.height;
-  messageBarInput.style.height = "auto";
-  messageBarInput.style.height = messageBarInput.scrollHeight +"px";
+    var old_height = window.getComputedStyle(messageBar).height
+    messageBarInput.style.height = "auto" /* <-- necessary, no clue why */
+    messageBarInput.style.height = messageBarInput.scrollHeight + "px"
+    var new_height = window.getComputedStyle(messageBar).height
 
-  if (is_at_bottom) {
-    messages.scrollTop = messages.scrollHeight;
-  }
+    var msgbar_size = window.getComputedStyle(document.body).getPropertyValue("--messagebar-size")
+    var total_size = parseInt(msgbar_size) + parseInt(new_height) - parseInt(old_height)
+
+    document.body.style.setProperty("--messagebar-size", total_size.toString() + "px")
+
+    if (is_at_bottom) {
+        messages.scrollTop = messages.scrollHeight
+    }
+}
+
+/**
+ * This event handler processes keydown events in the navbar. When pressed key is
+ * the enter key, send the message unless shift or control was pressed too.
+ *
+ * @param key the pressed key
+ */
+/* exported process_messagebar_keydown */
+function process_messagebar_keydown(key) {
+    key = key || event
+    var map = {}
+    map[key.keyCode] = key.type == "keydown"
+    if (key.ctrlKey || key.shiftKey) {
+        return true
+    }
+    if (map[13]) {
+        sendMessage()
+        key.preventDefault()
+    }
+    return true
+}
+
+/**
+ * Disable or enable textarea.
+ *
+ * @param isDisabled whether message bar should be enabled or disabled
+ */
+/* exported disableSendMessage */
+function disableSendMessage(isDisabled)
+{
+    messageBarInput.disabled = isDisabled
+}
+
+/**
+ * This event handler adds the hover property back to the "back to welcome view"
+ * button.
+ *
+ * This is a hack. It needs some explanations.
+ *
+ * Problem: Whenever the "back to welcome view" button is clicked, the webview
+ * freezes and the GTK ring welcome view is displayed. While the freeze
+ * itself is perfectly fine (probably necessary for good performances), this
+ * is a big problem for us when the user opens a chatview again: Since the
+ * chatview was freezed, the back button has «remembered» the hover state and
+ * still displays the blue background for a small instant. This is a very bad
+ * looking artefact.
+ *
+ * In order to counter this problem, we introduced the following evil mechanism:
+ * Whenever a user clicks on the "back to welcome view" button, the hover
+ * property is disabled. The hover property stays disabled until the user calls
+ * this event handler by hover-ing the button.
+ */
+/* exported addBackButtonHoverProperty */
+function addBackButtonHoverProperty()
+{
+    if(hoverBackButtonAllowed) {
+        backButton.classList.add("non-action-button")
+    }
 }
 
 /*
- * Update timestamps messages
+ * Update timestamps messages.
  */
 function updateView() {
-    updateTimestamps();
+    updateTimestamps()
 }
 
-setInterval(updateView, 60000);
+setInterval(updateView, 60000)
 
-window.onresize = function(event) {
-  updateTimestamps();
-};
+window.onresize = function() {
+    updateTimestamps()
+}
 
+/* exported addBannedContact */
 function addBannedContact()
 {
-  window.prompt('UNBLOCK');
+    window.prompt("UNBLOCK")
 }
 
+/* exported addToConversations */
 function addToConversations()
 {
-  window.prompt('ADD_TO_CONVERSATIONS');
+    window.prompt("ADD_TO_CONVERSATIONS")
 }
 
-function addBackButtonHoverProperty()
-{
-  if(hoverBackButtonAllowed) {
-      backButton.classList.add("non-action-button");
-  }
-}
-
+/* exported placeCall */
 function placeCall()
 {
-  window.prompt('PLACE_CALL');
+    window.prompt("PLACE_CALL")
 }
 
+/* exported placeAudioCall */
 function placeAudioCall()
 {
-  window.prompt('PLACE_AUDIO_CALL');
+    window.prompt("PLACE_AUDIO_CALL")
 }
 
+/* exported backToWelcomeView */
 function backToWelcomeView()
 {
-  backButton.classList.remove("non-action-button");
-  hoverBackButtonAllowed = false;
-  window.prompt('CLOSE_CHATVIEW');
-}
-
-function setDisplayLinks(display) {
-  displayLinksEnabled = display;
+    backButton.classList.remove("non-action-button")
+    hoverBackButtonAllowed = false
+    window.prompt("CLOSE_CHATVIEW")
 }
 
 /**
  * Transform a date to a string group like "1 hour ago".
+ *
+ * @param date
  */
 function formatDate(date) {
-    const seconds = Math.floor((new Date() - date) / 1000);
-    var interval = Math.floor(seconds / (3600 * 24));
+    const seconds = Math.floor((new Date() - date) / 1000)
+    var interval = Math.floor(seconds / (3600 * 24))
     if (interval > 5) {
-        return date.toLocaleDateString();
+        return date.toLocaleDateString()
     }
     if (interval > 1) {
-        return interval + ' days ago';
+        return interval + " days ago"
     }
     if (interval === 1) {
-        return interval + ' day ago';
+        return interval + " day ago"
     }
-    interval = Math.floor(seconds / 3600);
+    interval = Math.floor(seconds / 3600)
     if (interval > 1) {
-        return interval + ' hours ago';
+        return interval + " hours ago"
     }
     if (interval === 1) {
-        return interval + ' hour ago';
+        return interval + " hour ago"
     }
-    interval = Math.floor(seconds / 60);
+    interval = Math.floor(seconds / 60)
     if (interval > 1) {
-        return interval + ' minutes ago';
+        return interval + " minutes ago"
     }
-    return 'just now';
+    return "just now"
 }
 
 /**
  * Send #sendMessage #message value
  */
- function sendMessage()
- {
-     var message = messageBarInput.value;
-     if (message.length > 0) {
-         messageBarInput.value = '';
-         window.prompt('SEND:' + message);
-     }
- }
-
-/**
-* Disable or enable textarea
-*/
-function disableSendMessage(isDisabled)
+function sendMessage()
 {
-    messageBarInput.disabled = isDisabled;
+    var message = messageBarInput.value
+    if (message.length > 0) {
+        messageBarInput.value = ""
+        window.prompt("SEND:" + message)
+    }
 }
 
+/* exported acceptInvitation */
 function acceptInvitation()
 {
-    window.prompt('ACCEPT');
+    window.prompt("ACCEPT")
 }
 
+/* exported refuseInvitation */
 function refuseInvitation()
 {
-    window.prompt('REFUSE');
+    window.prompt("REFUSE")
 }
 
+/* exported blockConversation */
 function blockConversation()
 {
-    window.prompt('BLOCK');
+    window.prompt("BLOCK")
+}
+
+/* exported sendFile */
+function sendFile()
+{
+    window.prompt("SEND_FILE")
 }
 
 /**
- * Clears all messages
+ * Clear all messages.
  */
+/* exported clearMessages */
 function clearMessages()
 {
-    messages.innerHTML = "";
+    messages.innerHTML = ""
 }
 
 /**
- * Converts text to HTML
+ * Convert text to HTML.
  */
 function escapeHtml(html)
 {
-    var text = document.createTextNode(html);
-    var div = document.createElement('div');
-    div.appendChild(text);
-    return div.innerHTML;
+    var text = document.createTextNode(html)
+    var div = document.createElement("div")
+    div.appendChild(text)
+    return div.innerHTML
 }
 
 
 /**
- * Get the youtube video id from a URI
+ * Get the youtube video id from a URL.
+ * @param url
  */
 function youtube_id(url) {
-    const regExp = /^.*(youtu\.be\/|v\/|\/u\/w|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
-    const match = url.match(regExp);
-    return (match && match[2].length == 11) ? match[2] : null;
+    const regExp = /^.*(youtu\.be\/|v\/|\/u\/w|embed\/|watch\?v=|&v=)([^#&?]*).*/
+    const match = url.match(regExp)
+    return (match && match[2].length == 11) ? match[2] : null
 }
 
 /**
- * Returns HTML message from the message text.
- * Cleaned and linkified.
+ * Returns HTML message from the message text, cleaned and linkified.
+ * @param message_text
  */
 function getMessageHtml(message_text)
 {
-  const escaped_message = escapeHtml(message_text);
-  var linkified_message = linkifyHtml(escaped_message, {});
+    const escaped_message = escapeHtml(message_text)
+    var linkified_message = linkifyHtml(escaped_message, {})  // eslint-disable-line no-undef
 
-  const textPart = document.createElement('pre');
-  textPart.innerHTML = linkified_message;
+    const textPart = document.createElement("pre")
+    textPart.innerHTML = linkified_message
 
-  return textPart.outerHTML;
+    return textPart.outerHTML
 }
 
 /**
  * Returns the message status, formatted for display
+ * @param message_delivery_status
  */
+/* exported getMessageDeliveryStatusText */
 function getMessageDeliveryStatusText(message_delivery_status)
 {
-    var formatted_delivery_status = message_delivery_status;
+    var formatted_delivery_status = message_delivery_status
 
     switch(message_delivery_status)
     {
-        case "sending":
-        case "ongoing":
-            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>";
-            break;
-        case "failure":
-            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>";
-            break;
-        case "sent":
-        case "finished":
-        case "unknown":
-        case "read":
-            formatted_delivery_status = "";
-            break;
-        default:
-            console.log("getMessageDeliveryStatusText: unknown delivery status: " + message_delivery_status);
-            break;
+    case "sending":
+    case "ongoing":
+        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>"
+        break
+    case "failure":
+        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>"
+        break
+    case "sent":
+    case "finished":
+    case "unknown":
+    case "read":
+        formatted_delivery_status = ""
+        break
+    default:
+        break
     }
 
-    return formatted_delivery_status;
+    return formatted_delivery_status
 }
 
 /**
@@ -419,64 +504,68 @@
  */
 function getMessageTimestampText(message_timestamp, custom_format)
 {
-  const date = new Date(1000 * message_timestamp);
-  if(custom_format) {
-      return formatDate(date);
-  } else {
-      return date.toLocaleString();
-  }
+    const date = new Date(1000 * message_timestamp)
+    if(custom_format) {
+        return formatDate(date)
+    } else {
+        return date.toLocaleString()
+    }
 }
 
 /**
- * Merge timestamps if they are from the same group (likes: "1 hour ago")
+ * Merge timestamps if they are from the same group. For instance, several "1 hour ago"
+ * timestamps will be merged into a single one.
+ *
+ * @param baseNode
+ * @param endIndex
  */
 function cleanPreviousTimestamps (baseNode, endIndex) {
     // Remove previous elements with the same formatted timestamp
     for (var c = endIndex - 1 ; c >= 0 ; --c) {
-        const child = messages.children[c];
-        if (child.className.indexOf('timestamp') !== -1) {
+        const child = messages.children[c]
+        if (child.className.indexOf("timestamp") !== -1) {
             if (child.className === baseNode.className &&
             child.innerHTML === baseNode.innerHTML) {
-                messages.removeChild(child);
+                messages.removeChild(child)
             } else {
-                break; // A different timestamp is met, we can stop here.
+                break // A different timestamp is met, we can stop here.
             }
         }
     }
 }
 
 /**
- * Update timestamps
+ * Update timestamps.
  */
 function updateTimestamps() {
-    const timestamps = messages.querySelectorAll('.timestamp');
-    const is_image_out = messages.querySelector('.message_out .sender_image')
-    const is_image_in = messages.querySelector('.message_in .sender_image')
+    const timestamps = messages.getElementsByClassName(".timestamp")
+    const is_image_out = messages.querySelector(".message_out .sender_image")
+    const is_image_in = messages.querySelector(".message_in .sender_image")
     if (timestamps) {
         for ( var c = 0 ; c < messages.children.length ; ++c) {
-          const child = messages.children[c];
-          if (child.className.indexOf('timestamp') !== -1) {
+            const child = messages.children[c]
+            if (child.className.indexOf("timestamp") !== -1) {
                 // Update timestamp
-                child.innerHTML = getMessageTimestampText(child.getAttribute('message_timestamp'), true)
+                child.innerHTML = getMessageTimestampText(child.getAttribute("message_timestamp"), true)
 
-                var desktop_margin = '25%'
+                var desktop_margin = "25%"
                 const height = document.body.clientHeight
                 const width = document.body.clientWidth
                 if (width <= 1920 || height <= 1080) {
-                  desktop_margin = '15%'
+                    desktop_margin = "15%"
                 }
                 if (width <= 1000 || height <= 480) {
-                  desktop_margin = '0px'
+                    desktop_margin = "0px"
                 }
-                if (child.className.indexOf('timestamp_out') !== -1) {
-                  const avatar_px = is_image_out ? (is_image_out.offsetHeight === avatar_size ? '60px' : '20px') : '20px'
-                  child.style.paddingRight = `calc(${desktop_margin} + ${avatar_px})`
-                } else if (child.className.indexOf('timestamp_in') !== -1) {
-                  const avatar_px = is_image_in ? (is_image_in.offsetHeight === avatar_size ? '60px' : '20px') : '20px'
-                  child.style.paddingLeft = `calc(${desktop_margin} + ${avatar_px})`
+                if (child.className.indexOf("timestamp_out") !== -1) {
+                    const avatar_px = is_image_out ? (is_image_out.offsetHeight === avatar_size ? "60px" : "20px") : "20px"
+                    child.style.paddingRight = `calc(${desktop_margin} + ${avatar_px})`
+                } else if (child.className.indexOf("timestamp_in") !== -1) {
+                    const avatar_px = is_image_in ? (is_image_in.offsetHeight === avatar_size ? "60px" : "20px") : "20px"
+                    child.style.paddingLeft = `calc(${desktop_margin} + ${avatar_px})`
                 }
                 // Remove previous elements with the same formatted timestamp
-                cleanPreviousTimestamps(child, c);
+                cleanPreviousTimestamps(child, c)
             }
         }
     }
@@ -485,43 +574,47 @@
 /**
  * Convert a value in filesize
  */
- function humanFileSize(bytes) {
-    var thresh = 1024;
+function humanFileSize(bytes) {
+    var thresh = 1024
     if(Math.abs(bytes) < thresh) {
-        return bytes + ' B';
+        return bytes + " B"
     }
-    var units = ['kB','MB','GB','TB','PB','EB','ZB','YB']
-    var u = -1;
+    var units = ["kB","MB","GB","TB","PB","EB","ZB","YB"]
+    var u = -1
     do {
-        bytes /= thresh;
-        ++u;
-    } while(Math.abs(bytes) >= thresh && u < units.length - 1);
-    return bytes.toFixed(1)+' '+units[u];
+        bytes /= thresh
+        ++u
+    } while(Math.abs(bytes) >= thresh && u < units.length - 1)
+    return bytes.toFixed(1)+" "+units[u]
 }
 
 /**
- * Change the value of the progress bar
+ * Change the value of the progress bar.
+ *
+ * @param progress_bar
+ * @param message_object
  */
-function updateProgressBar(progress_bar, message_object, message_delivery_status) {
-  var delivery_status = message_object["delivery_status"];
-  if ("progress" in message_object && !isErrorStatus(delivery_status) && message_object["progress"] !== 100) {
-    var progress_percent = (100 * message_object["progress"] / message_object["totalSize"]);
-    if (progress_percent !== 100)
-      progress_bar.childNodes[0].setAttribute("style", "width: " + progress_percent + "%");
-    else
-      progress_bar.setAttribute("style", "display: none");
-  } else
-    progress_bar.setAttribute("style", "display: none");
+function updateProgressBar(progress_bar, message_object) {
+    var delivery_status = message_object["delivery_status"]
+    if ("progress" in message_object && !isErrorStatus(delivery_status) && message_object["progress"] !== 100) {
+        var progress_percent = (100 * message_object["progress"] / message_object["totalSize"])
+        if (progress_percent !== 100)
+            progress_bar.childNodes[0].setAttribute("style", "width: " + progress_percent + "%")
+        else
+            progress_bar.setAttribute("style", "display: none")
+    } else
+        progress_bar.setAttribute("style", "display: none")
 }
 
 /**
  * Check if a status is an error status
+ * @param
  */
 function isErrorStatus(status) {
-  return (status === 'failure'
-         || status === 'awaiting peer timeout'
-         || status === 'canceled'
-         || status === 'unjoinable peer');
+    return (status === "failure"
+         || status === "awaiting peer timeout"
+         || status === "canceled"
+         || status === "unjoinable peer")
 }
 
 /**
@@ -529,142 +622,147 @@
  * @param message_id
  */
 function fileInteraction(message_id) {
-  var message_wrapper = document.createElement('div')
-  message_wrapper.setAttribute('class', 'message_wrapper')
+    var message_wrapper = document.createElement("div")
+    message_wrapper.setAttribute("class", "message_wrapper")
 
-  // An file interaction contains buttons at the left of the interaction
-  // for the status or accept/refuse
-  var left_buttons = document.createElement('div')
-  left_buttons.setAttribute('class', 'left_buttons')
-  message_wrapper.appendChild(left_buttons)
-  // Also contains a bold clickable text
-  var text_div = document.createElement('div')
-  text_div.setAttribute('class', 'text')
-  text_div.addEventListener('click', function (event) {
-    // ask ring to open the file
-    const filename = document.querySelector('#message_' + message_id + ' .full').innerText
-    window.prompt('OPEN_FILE:' + filename)
-  });
-  var full_div = document.createElement('div')
-  full_div.setAttribute('class', 'full')
-  full_div.style = 'visibility: hidden; display: none;'
-  var message_text = document.createElement('div')
-  message_text.setAttribute('class', 'filename')
-  // And some informations like size or error message.
-  var informations_div = document.createElement('div')
-  informations_div.setAttribute('class', 'informations')
-  text_div.appendChild(message_text)
-  text_div.appendChild(full_div)
-  text_div.appendChild(informations_div)
-  message_wrapper.appendChild(text_div)
-  // And finally, a progress bar
-  message_transfer_progress_bar = document.createElement('span')
-  message_transfer_progress_bar.setAttribute('class', 'message_progress_bar')
-  message_transfer_progress_completion = document.createElement('span')
-  message_transfer_progress_bar.appendChild(message_transfer_progress_completion)
-  message_wrapper.appendChild(message_transfer_progress_bar)
-  return message_wrapper
+    /* A file interaction contains buttons at the left for status information
+       or accept/refuse actions. The text is bold and clickable. */
+    var left_buttons = document.createElement("div")
+    left_buttons.setAttribute("class", "left_buttons")
+    message_wrapper.appendChild(left_buttons)
+    var text_div = document.createElement("div")
+    text_div.setAttribute("class", "text")
+
+    text_div.addEventListener("click", function () {
+        // ask ring to open the file
+        const filename = document.querySelector("#message_" + message_id + " .full").innerText
+        window.prompt("OPEN_FILE:" + filename)
+    })
+
+    var full_div = document.createElement("div")
+    full_div.setAttribute("class", "full")
+    full_div.style = "visibility: hidden; display: none;"
+    var message_text = document.createElement("div")
+    message_text.setAttribute("class", "filename")
+
+    // And some informations like size or error message.
+    var informations_div = document.createElement("div")
+    informations_div.setAttribute("class", "informations")
+    text_div.appendChild(message_text)
+    text_div.appendChild(full_div)
+    text_div.appendChild(informations_div)
+    message_wrapper.appendChild(text_div)
+
+    // And finally, a progress bar
+    var message_transfer_progress_bar = document.createElement("span")
+    message_transfer_progress_bar.setAttribute("class", "message_progress_bar")
+    var message_transfer_progress_completion = document.createElement("span")
+    message_transfer_progress_bar.appendChild(message_transfer_progress_completion)
+    message_wrapper.appendChild(message_transfer_progress_bar)
+    return message_wrapper
 }
 
 /**
  * Update a file interaction (icons + filename + status + progress bar)
+ *
  * @param message_div the message to update
  * @param message_object new informations
+ * @param forceTypeToFile
  */
- function updateFileInteraction(message_div, message_object, forceTypeToFile = false) {
-   if (!message_div.querySelector('.informations')) return // media
+function updateFileInteraction(message_div, message_object, forceTypeToFile = false) {
+    if (!message_div.querySelector(".informations")) return // media
 
-   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>',
-       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>',
-       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>',
-       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>'
-   var message_delivery_status = message_object["delivery_status"]
-   var message_direction = message_object["direction"]
-   var message_id = message_object["id"]
-   var message_text = message_object['text']
+    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>",
+        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>",
+        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>",
+        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>"
+    var message_delivery_status = message_object["delivery_status"]
+    var message_direction = message_object["direction"]
+    var message_id = message_object["id"]
+    var message_text = message_object["text"]
 
 
-   if (isImage(message_text) && message_delivery_status === 'finished' && displayLinksEnabled && !forceTypeToFile) {
-     // Replace the old wrapper by the downloaded image
-     if (message_div.querySelector('.message_wrapper')) {
-       wrapper = message_div.querySelector('.message_wrapper')
-       wrapper.parentNode.removeChild(wrapper)
-     }
-     message_div.append(mediaInteraction(message_id, message_text))
-     message_div.querySelector('img').id = message_id
-     message_div.querySelector('img').msg_obj = message_object
-     message_div.querySelector('img').onerror = function() {
-       message_div = document.getElementById('#message_' + this.id);
-       if (message_div.querySelector('.message_wrapper')) {
-         wrapper = message_div.querySelector('.message_wrapper')
-         wrapper.parentNode.removeChild(wrapper)
-       }
-       message_div.append(fileInteraction(message_id))
-       updateFileInteraction(message_div, this.msg_obj, true)
-     }
-     return
-   }
+    if (isImage(message_text) && message_delivery_status === "finished" && displayLinksEnabled && !forceTypeToFile) {
+        // Replace the old wrapper by the downloaded image
+        if (message_div.querySelector(".message_wrapper")) {
+            var wrapper = message_div.querySelector(".message_wrapper")
+            wrapper.parentNode.removeChild(wrapper)
+        }
+        message_div.append(mediaInteraction(message_id, message_text))
+        message_div.querySelector("img").id = message_id
+        message_div.querySelector("img").msg_obj = message_object
+        message_div.querySelector("img").onerror = function() {
+            message_div = document.getElementById("#message_" + this.id)
+            var wrapper = message_div.querySelector(".message_wrapper")
+            if (wrapper) {
+                wrapper.parentNode.removeChild(wrapper)
+            }
+            message_div.append(fileInteraction(message_id))
+            updateFileInteraction(message_div, this.msg_obj, true)
+        }
+        return
+    }
 
-   // Set informations text
-   var informations_div = message_div.querySelector(".informations");
-   var informations_txt = getMessageTimestampText(message_object["timestamp"], true);
-   if (message_object["totalSize"] && message_object["progress"]) {
-     if (message_delivery_status === 'finished') {
-       informations_txt += " - " + humanFileSize(message_object["totalSize"]);
-     } else {
-       informations_txt += " - " + humanFileSize(message_object["progress"])
-                         + " / " + humanFileSize(message_object["totalSize"]);
-     }
-   }
-   informations_txt += " - " + message_delivery_status;
-   informations_div.innerText = informations_txt;
+    // Set informations text
+    var informations_div = message_div.querySelector(".informations")
+    var informations_txt = getMessageTimestampText(message_object["timestamp"], true)
+    if (message_object["totalSize"] && message_object["progress"]) {
+        if (message_delivery_status === "finished") {
+            informations_txt += " - " + humanFileSize(message_object["totalSize"])
+        } else {
+            informations_txt += " - " + humanFileSize(message_object["progress"])
+                         + " / " + humanFileSize(message_object["totalSize"])
+        }
+    }
+    informations_txt += " - " + message_delivery_status
+    informations_div.innerText = informations_txt
 
-   // Update flat buttons
-   var left_buttons = message_div.querySelector(".left_buttons");
-   left_buttons.innerHTML = '';
-   if (message_delivery_status === 'awaiting peer' || message_delivery_status === 'awaiting host' || message_delivery_status.indexOf('ongoing') === 0) {
-     if (message_direction === 'in' && message_delivery_status.indexOf('ongoing') !== 0) {
-       // add buttons to accept or refuse a call.
-       var accept_button = document.createElement('div');
-       accept_button.innerHTML = acceptSvg;
-       accept_button.setAttribute("title", "Accept");
-       accept_button.setAttribute("class", "flat-button accept");
-       accept_button.onclick = function() {
-         window.prompt('ACCEPT_FILE:' + message_id);
-       }
-       left_buttons.appendChild(accept_button);
-     }
-     var refuse_button = document.createElement('div');
-     refuse_button.innerHTML = refuseSvg;
-     refuse_button.setAttribute("title", "Refuse");
-     refuse_button.setAttribute("class", "flat-button refuse");
-     refuse_button.onclick = function() {
-       window.prompt('REFUSE_FILE:' + message_id);
-     }
-     left_buttons.appendChild(refuse_button);
-   } else {
-     var status_button = document.createElement('div');
-     var statusFile = fileSvg;
-     if (isErrorStatus(message_delivery_status))
-       statusFile = warningSvg;
-     status_button.innerHTML = statusFile;
-     status_button.setAttribute("class", "flat-button");
-     left_buttons.appendChild(status_button);
-   }
+    // Update flat buttons
+    var left_buttons = message_div.querySelector(".left_buttons")
+    left_buttons.innerHTML = ""
+    if (message_delivery_status === "awaiting peer" || message_delivery_status === "awaiting host" || message_delivery_status.indexOf("ongoing") === 0) {
+        if (message_direction === "in" && message_delivery_status.indexOf("ongoing") !== 0) {
+            // add buttons to accept or refuse a call.
+            var accept_button = document.createElement("div")
+            accept_button.innerHTML = acceptSvg
+            accept_button.setAttribute("title", "Accept")
+            accept_button.setAttribute("class", "flat-button accept")
+            accept_button.onclick = function() {
+                window.prompt("ACCEPT_FILE:" + message_id)
+            }
+            left_buttons.appendChild(accept_button)
+        }
+        var refuse_button = document.createElement("div")
+        refuse_button.innerHTML = refuseSvg
+        refuse_button.setAttribute("title", "Refuse")
+        refuse_button.setAttribute("class", "flat-button refuse")
+        refuse_button.onclick = function() {
+            window.prompt("REFUSE_FILE:" + message_id)
+        }
+        left_buttons.appendChild(refuse_button)
+    } else {
+        var status_button = document.createElement("div")
+        var statusFile = fileSvg
+        if (isErrorStatus(message_delivery_status))
+            statusFile = warningSvg
+        status_button.innerHTML = statusFile
+        status_button.setAttribute("class", "flat-button")
+        left_buttons.appendChild(status_button)
+    }
 
-   message_div.querySelector('.full').innerText = message_text
-   message_div.querySelector('.filename').innerText = message_text.split('/').pop()
-   message_div.querySelector('.filename').innerText = message_text.split('/').pop()
-   updateProgressBar(message_div.querySelector('.message_progress_bar'), message_object);
-   updateProgressBar(message_div.querySelector('.message_progress_bar'), message_object);
- }
+    message_div.querySelector(".full").innerText = message_text
+    message_div.querySelector(".filename").innerText = message_text.split("/").pop()
+    message_div.querySelector(".filename").innerText = message_text.split("/").pop()
+    updateProgressBar(message_div.querySelector(".message_progress_bar"), message_object)
+    updateProgressBar(message_div.querySelector(".message_progress_bar"), message_object)
+}
 
 /**
  * Return if a file is an image
  * @param file
  */
 function isImage(file) {
-  return file.toLowerCase().match(/\.(jpeg|jpg|gif|png)$/) !== null
+    return file.toLowerCase().match(/\.(jpeg|jpg|gif|png)$/) !== null
 }
 
 /**
@@ -672,11 +770,11 @@
  * @param file
  */
 function isVideo(file) {
-  const availableProtocols = ['http:', 'https:']
-  const videoHostname = ['youtube.com', 'www.youtube.com', 'youtu.be']
-  const urlParser = document.createElement('a')
-  urlParser.href = file
-  return (availableProtocols.includes(urlParser.protocol) && videoHostname.includes(urlParser.hostname))
+    const availableProtocols = ["http:", "https:"]
+    const videoHostname = ["youtube.com", "www.youtube.com", "youtu.be"]
+    const urlParser = document.createElement("a")
+    urlParser.href = file
+    return (availableProtocols.includes(urlParser.protocol) && videoHostname.includes(urlParser.hostname))
 }
 
 /**
@@ -684,43 +782,44 @@
  * @param message_id
  * @param link to show
  * @param ytid if it's a youtube video
+ * @param
  */
 function mediaInteraction(message_id, link, ytid, noerror) {
-  // TODO promise?
-  // Try to display images.
-  const media_wrapper = document.createElement('div')
-  media_wrapper.setAttribute('class', 'media_wrapper')
-  const linkElt = document.createElement('a')
-  linkElt.href = link
-  linkElt.style = 'text-decoration: none;'
-  const imageElt = document.createElement('img')
-  // Note, here, we don't check the size of the image.
-  // in the future, we can check the content-type and content-length with a request
-  // and maybe disable svg
-  if (noerror)
-    imageElt.setAttribute('onerror', 'this.style.display=\'none\'')
-  if (ytid) {
-    imageElt.src = `http://img.youtube.com/vi/${ytid}/0.jpg`
-  } else {
-    imageElt.src = link
-  }
-  linkElt.appendChild(imageElt)
-  if (ytid) {
-    const containerElt = document.createElement('div');
-    containerElt.setAttribute('class', 'containerVideo');
-    const playDiv = document.createElement('div')
-    playDiv.setAttribute('class', 'playVideo')
-    playDiv.innerHTML = '<svg fill="#ffffff" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">\
-        <path d="M8 5v14l11-7z"/>\
-        <path d="M0 0h24v24H0z" fill="none"/>\
-    </svg>'
-    linkElt.appendChild(playDiv)
-    containerElt.appendChild(linkElt)
-    media_wrapper.appendChild(containerElt)
-  } else {
-    media_wrapper.appendChild(linkElt)
-  }
-  return media_wrapper
+    /* TODO promise?
+     Try to display images. */
+    const media_wrapper = document.createElement("div")
+    media_wrapper.setAttribute("class", "media_wrapper")
+    const linkElt = document.createElement("a")
+    linkElt.href = link
+    linkElt.style = "text-decoration: none; border:none;"
+    const imageElt = document.createElement("img")
+    /* Note, here, we don't check the size of the image.
+     in the future, we can check the content-type and content-length with a request
+     and maybe disable svg */
+    if (noerror)
+        imageElt.setAttribute("onerror", "this.style.display='none'")
+    if (ytid) {
+        imageElt.src = `http://img.youtube.com/vi/${ytid}/0.jpg`
+    } else {
+        imageElt.src = link
+    }
+    linkElt.appendChild(imageElt)
+    if (ytid) {
+        const containerElt = document.createElement("div")
+        containerElt.setAttribute("class", "containerVideo")
+        const playDiv = document.createElement("div")
+        playDiv.setAttribute("class", "playVideo")
+        playDiv.innerHTML = "<svg fill=\"#ffffff\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\
+        <path d=\"M8 5v14l11-7z\"/>\
+        <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\
+    </svg>"
+        linkElt.appendChild(playDiv)
+        containerElt.appendChild(linkElt)
+        media_wrapper.appendChild(containerElt)
+    } else {
+        media_wrapper.appendChild(linkElt)
+    }
+    return media_wrapper
 }
 
 /**
@@ -729,14 +828,14 @@
  * @param htmlText the DOM to show
  */
 function textInteraction(message_id, htmlText) {
-  const message_wrapper = document.createElement('div')
-  message_wrapper.setAttribute('class', 'message_wrapper')
-  var message_text = document.createElement('span')
-  message_text.setAttribute('class', 'message_text')
-  message_text.innerHTML = htmlText
-  message_wrapper.appendChild(message_text)
-  // TODO STATUS
-  return message_wrapper
+    const message_wrapper = document.createElement("div")
+    message_wrapper.setAttribute("class", "message_wrapper")
+    var message_text = document.createElement("span")
+    message_text.setAttribute("class", "message_text")
+    message_text.innerHTML = htmlText
+    message_wrapper.appendChild(message_text)
+    // TODO STATUS
+    return message_wrapper
 }
 
 /**
@@ -746,69 +845,65 @@
  * @TODO retry button
  */
 function updateTextInteraction(message_div, delivery_status) {
-  if (!message_div.querySelector('.message_text')) return // media
-  switch(delivery_status)
-  {
+    if (!message_div.querySelector(".message_text")) return // media
+    var sending = message_div.querySelector(".sending")
+    switch(delivery_status)
+    {
     case "ongoing":
     case "sending":
-      var sending_div = message_div.querySelector('.sending')
-      if (!sending_div) {
-        sending_div = document.createElement('div')
-        sending_div.setAttribute('class', 'sending')
-        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>'
-        // add sending animation to message
-        message_div.appendChild(sending_div)
-      }
-      message_div.querySelector('.message_text').style = 'color: #888'
-      break
+        if (!sending) {
+            sending = document.createElement("div")
+            sending.setAttribute("class", "sending")
+            sending.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>"
+            // add sending animation to message;
+            message_div.appendChild(sending)
+        }
+        message_div.querySelector(".message_text").style = "color: #888"
+        break
     case "failure":
-      // change text color to red
-      message_div.querySelector('.message_wrapper').style = 'background-color: #f3a6a6'
-      message_div.querySelector('.message_text').style = 'color: #000'
-      var failure_div = message_div.querySelector('.failure')
-      if (!failure_div) {
-        failure_div = document.createElement('div')
-        failure_div.setAttribute('class', 'failure')
-        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>'
-        // add failure animation to message
-        message_div.appendChild(failure_div)
-      }
-      var sending = message_div.querySelector('.sending')
-      if (sending) sending.style.visibility = 'hidden'
-      break
+        // change text color to red
+        message_div.querySelector(".message_wrapper").style = "background-color: #f3a6a6"
+        message_div.querySelector(".message_text").style = "color: #000"
+        var failure_div = message_div.querySelector(".failure")
+        if (!failure_div) {
+            failure_div = document.createElement("div")
+            failure_div.setAttribute("class", "failure")
+            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>"
+            // add failure animation to message
+            message_div.appendChild(failure_div)
+        }
+        if (sending) sending.style.display = "none"
+        break
     case "sent":
     case "finished":
     case "unknown":
     case "read":
-      // change text color to black
-      message_div.querySelector('.message_text').style = 'color: #000'
-      var sending = message_div.querySelector('.sending')
-      if (sending) sending.style.visibility = 'hidden'
-      break
+        // change text color to black
+        message_div.querySelector(".message_text").style = "color: #000"
+        if (sending) sending.style.display = "none"
+        break
     default:
-      console.log("getMessageDeliveryStatusText: unknown delivery status: " + delivery_status)
-      break
-  }
+        break
+    }
 }
 
 /**
  * Build a new interaction (call or contact)
- * @param message_id
  */
-function actionInteraction(message_id) {
-  var message_wrapper = document.createElement('div')
-  message_wrapper.setAttribute('class', 'message_wrapper')
+function actionInteraction() {
+    var message_wrapper = document.createElement("div")
+    message_wrapper.setAttribute("class", "message_wrapper")
 
-  // An file interaction contains buttons at the left of the interaction
-  // for the status or accept/refuse
-  var left_buttons = document.createElement('div')
-  left_buttons.setAttribute('class', 'left_buttons')
-  message_wrapper.appendChild(left_buttons)
-  // Also contains a bold clickable text
-  var text_div = document.createElement('div')
-  text_div.setAttribute('class', 'text')
-  message_wrapper.appendChild(text_div)
-  return message_wrapper
+    // An file interaction contains buttons at the left of the interaction
+    // for the status or accept/refuse
+    var left_buttons = document.createElement("div")
+    left_buttons.setAttribute("class", "left_buttons")
+    message_wrapper.appendChild(left_buttons)
+    // Also contains a bold clickable text
+    var text_div = document.createElement("div")
+    text_div.setAttribute("class", "text")
+    message_wrapper.appendChild(text_div)
+    return message_wrapper
 }
 
 /**
@@ -817,28 +912,28 @@
  * @param message_object new informations
  */
 function updateCallInteraction(message_div, message_object) {
-  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>'
-  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>'
-  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>'
-  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>'
+    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>"
+    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>"
+    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>"
+    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>"
 
-  const message_text = message_object['text']
-  const message_direction = (message_text.toLowerCase().indexOf('incoming') !== -1) ? 'in' : 'out'
-  const missed = message_text.indexOf('Missed') !== -1
+    const message_text = message_object["text"]
+    const message_direction = (message_text.toLowerCase().indexOf("incoming") !== -1) ? "in" : "out"
+    const missed = message_text.indexOf("Missed") !== -1
 
-  message_div.querySelector('.text').innerText = message_text.substring(2)
+    message_div.querySelector(".text").innerText = message_text.substring(2)
 
-  var left_buttons = message_div.querySelector('.left_buttons')
-  left_buttons.innerHTML = ''
-  var status_button = document.createElement('div')
-  var statusFile = ''
-  if (missed)
-    statusFile = (message_direction === 'in') ? callMissed : outgoingMissed
-  else
-    statusFile = (message_direction === 'in') ? callReceived : outgoingCall
-  status_button.innerHTML = statusFile
-  status_button.setAttribute('class', 'flat-button')
-  left_buttons.appendChild(status_button)
+    var left_buttons = message_div.querySelector(".left_buttons")
+    left_buttons.innerHTML = ""
+    var status_button = document.createElement("div")
+    var statusFile = ""
+    if (missed)
+        statusFile = (message_direction === "in") ? callMissed : outgoingMissed
+    else
+        statusFile = (message_direction === "in") ? callReceived : outgoingCall
+    status_button.innerHTML = statusFile
+    status_button.setAttribute("class", "flat-button")
+    left_buttons.appendChild(status_button)
 }
 
 /**
@@ -847,18 +942,18 @@
  * @param message_object new informations
  */
 function updateContactInteraction(message_div, message_object) {
-  const message_text = message_object['text']
+    const message_text = message_object["text"]
 
-  message_div.querySelector('.text').innerText = message_text
+    message_div.querySelector(".text").innerText = message_text
 
-  var left_buttons = message_div.querySelector('.left_buttons')
-  left_buttons.innerHTML = ''
-  var status_button = document.createElement('div')
-  status_button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">\
-<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"/>\
-<path d="M0 0h24v24H0z" fill="none"/></svg>'
-  status_button.setAttribute('class', 'flat-button')
-  left_buttons.appendChild(status_button)
+    var left_buttons = message_div.querySelector(".left_buttons")
+    left_buttons.innerHTML = ""
+    var status_button = document.createElement("div")
+    status_button.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\
+<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\"/>\
+<path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>"
+    status_button.setAttribute("class", "flat-button")
+    left_buttons.appendChild(status_button)
 }
 
 /**
@@ -868,218 +963,219 @@
  * @param insert_after if we want the message at the end or the top of the conversation
  */
 function addOrUpdateMessage(message_object, new_message, insert_after = true) {
-  const message_id = message_object['id']
-  const message_type = message_object['type']
-  const message_text = message_object['text']
-  const message_direction = message_object['direction']
-  const message_timestamp = message_object['timestamp']
-  const delivery_status = message_object['delivery_status']
-  const message_sender_contact_method = message_object['sender_contact_method']
+    const message_id = message_object["id"]
+    const message_type = message_object["type"]
+    const message_text = message_object["text"]
+    const message_direction = message_object["direction"]
+    const message_timestamp = message_object["timestamp"]
+    const delivery_status = message_object["delivery_status"]
+    const message_sender_contact_method = message_object["sender_contact_method"]
 
-  var message_div = document.querySelector('#message_' + message_id);
-  var type = ''
-  if (new_message) {
+    var message_div = document.getElementById("message_" + message_id)
+    var type = ""
+    if (new_message) {
     // Build new message
-    var classes = [
-        'message',
-        `message_${message_direction}`,
-        `message_type_${message_type}`
-    ]
+        var classes = [
+            "message",
+            `message_${message_direction}`,
+            `message_type_${message_type}`
+        ]
 
-    message_div = document.createElement('div')
-    message_div.setAttribute('id', `message_${message_id}`)
-    message_div.setAttribute('class', classes.join(' '))
+        message_div = document.createElement("div")
+        message_div.setAttribute("id", `message_${message_id}`)
+        message_div.setAttribute("class", classes.join(" "))
 
-    // Build message for each types.
-    // Add sender images if necessary (like if the interaction doesn't take the whole width)
-    const need_sender = (message_type === 'data_transfer' || message_type === 'text');
-    if (need_sender) {
-      var message_sender_image = document.createElement('span')
-      message_sender_image.setAttribute('class', `sender_image sender_image_${message_sender_contact_method}`)
-      message_div.appendChild(message_sender_image)
-    }
-
-    // Build main content
-    if (message_type === 'data_transfer') {
-      if (isImage(message_text) && delivery_status === 'finished' && displayLinksEnabled) {
-        message_div.append(mediaInteraction(message_id, message_text, null, false))
-        message_div.querySelector('img').id = message_id
-        message_div.querySelector('img').msg_obj = message_object
-        message_div.querySelector('img').onerror = function() {
-          message_div = document.getElementById('#message_' + this.id);
-          if (message_div.querySelector('.message_wrapper')) {
-            wrapper = message_div.querySelector('.message_wrapper')
-            wrapper.parentNode.removeChild(wrapper)
-          }
-          message_div.append(fileInteraction(message_id))
-          updateFileInteraction(message_div, this.msg_obj, true)
+        // Build message for each types.
+        // Add sender images if necessary (like if the interaction doesn't take the whole width)
+        const need_sender = (message_type === "data_transfer" || message_type === "text")
+        if (need_sender) {
+            var message_sender_image = document.createElement("span")
+            message_sender_image.setAttribute("class", `sender_image sender_image_${message_sender_contact_method}`)
+            message_div.appendChild(message_sender_image)
         }
-      } else {
-        message_div.append(fileInteraction(message_id))
-      }
-    } else if (message_type === 'text') {
-      // TODO add the possibility to update messages (remove and rebuild)
-      const htmlText = getMessageHtml(message_text)
-      if (displayLinksEnabled) {
-        const parser = new DOMParser();
-        const DOMMsg = parser.parseFromString(htmlText, 'text/xml');
-        const links = DOMMsg.querySelectorAll('a');
-        if (DOMMsg.childNodes.length && links.length) {
-          var isTextToShow = (DOMMsg.childNodes[0].childNodes.length > 1)
-          const ytid = (isVideo(message_text))? youtube_id(message_text) : ''
-          if (!isTextToShow && (ytid || isImage(message_text))) {
-            type = 'media'
-            message_div.append(mediaInteraction(message_id, message_text, ytid))
-          }
+
+        // Build main content
+        if (message_type === "data_transfer") {
+            if (isImage(message_text) && delivery_status === "finished" && displayLinksEnabled) {
+                message_div.append(mediaInteraction(message_id, message_text, null, false))
+                message_div.querySelector("img").id = message_id
+                message_div.querySelector("img").msg_obj = message_object
+                message_div.querySelector("img").onerror = function() {
+                    message_div = document.getElementById("#message_" + this.id)
+                    /* FIXME in many cases, message_div is null here and this code doesn't do anything */
+                    if (message_div.querySelector(".message_wrapper")) {
+                        var wrapper = message_div.querySelector(".message_wrapper")
+                        wrapper.parentNode.removeChild(wrapper)
+                    }
+                    message_div.append(fileInteraction(message_id))
+                    updateFileInteraction(message_div, this.msg_obj, true)
+                }
+            } else {
+                message_div.append(fileInteraction(message_id))
+            }
+        } else if (message_type === "text") {
+            // TODO add the possibility to update messages (remove and rebuild)
+            const htmlText = getMessageHtml(message_text)
+            if (displayLinksEnabled) {
+                const parser = new DOMParser()
+                const DOMMsg = parser.parseFromString(htmlText, "text/xml")
+                const links = DOMMsg.querySelectorAll("a")
+                if (DOMMsg.childNodes.length && links.length) {
+                    var isTextToShow = (DOMMsg.childNodes[0].childNodes.length > 1)
+                    const ytid = (isVideo(message_text))? youtube_id(message_text) : ""
+                    if (!isTextToShow && (ytid || isImage(message_text))) {
+                        type = "media"
+                        message_div.append(mediaInteraction(message_id, message_text, ytid))
+                    }
+                }
+            }
+            if (type !== "media") {
+                type = "text"
+                message_div.append(textInteraction(message_id, htmlText))
+            }
+        } else if (message_type === "call" || message_type === "contact") {
+            message_div.append(actionInteraction())
+        } else {
+            const temp = document.createElement("div")
+            temp.innerText = message_type
+            message_div.appendChild(temp)
         }
-      }
-      if (type !== 'media') {
-        type = 'text'
-        message_div.append(textInteraction(message_id, htmlText))
-      }
-    } else if (message_type === 'call' || message_type === 'contact') {
-      message_div.append(actionInteraction(message_id))
-    } else {
-      const temp = document.createElement('div')
-      temp.innerText = message_type
-      message_div.appendChild(temp)
+
+        // Get timestamp to add
+        const formattedTimestamp = getMessageTimestampText(message_timestamp, true)
+        // Create the timestamp object
+        const date_elt = document.createElement("div")
+        date_elt.innerText = formattedTimestamp
+        var typeIsCallOrContact = (message_type === "call" || message_type === "contact")
+        var timestamp_div_classes = ["timestamp", typeIsCallOrContact ? "timestamp_action" : `timestamp_${message_direction}`]
+        date_elt.setAttribute("class", timestamp_div_classes.join(" "))
+        date_elt.setAttribute("message_timestamp", message_timestamp)
+
+        // Remove last timestamp if it's the same<h6></h6>
+        if (messages.getElementsByClassName(".timestamp"))
+            cleanPreviousTimestamps(date_elt, messages.children.length)
+
+        // Add message and the new timestamp
+        if (insert_after) {
+            if (message_type === "call" || message_type === "contact") {
+                message_div.querySelector(".message_wrapper").appendChild(date_elt)
+                messages.appendChild(message_div)
+            } else {
+                messages.appendChild(message_div)
+                messages.appendChild(date_elt)
+            }
+        } else {
+            if (message_type === "call" || message_type === "contact") {
+                message_div.querySelector(".message_wrapper").appendChild(date_elt)
+                messages.insertBefore(message_div, messages.firstChild)
+            } else {
+                messages.insertBefore(date_elt, messages.firstChild)
+                messages.insertBefore(message_div, messages.firstChild)
+            }
+        }
     }
 
-    // Get timestamp to add
-    const formattedTimestamp = getMessageTimestampText(message_timestamp, true)
-    // Create the timestamp object
-    const date_elt = document.createElement('div')
-    date_elt.innerText = formattedTimestamp
-    if (message_type === 'call' || message_type === 'contact') {
-      timestamp_div_classes = [
-        'timestamp',
-        `timestamp_action`
-      ]
-    } else {
-      timestamp_div_classes = [
-        'timestamp',
-        `timestamp_${message_direction}`
-      ]
-    }
-    date_elt.setAttribute('class', timestamp_div_classes.join(' '))
-    date_elt.setAttribute('message_timestamp', message_timestamp)
-    // Remove last timestamp if it's the same<h6></h6>
-    if (messages.querySelectorAll('.timestamp'))
-        cleanPreviousTimestamps(date_elt, messages.children.length)
-
-    // Add message and the new timestamp
-    if (insert_after) {
-      if (message_type === 'call' || message_type === 'contact') {
-        message_div.querySelector('.message_wrapper').appendChild(date_elt)
-        messages.appendChild(message_div)
-      } else {
-        messages.appendChild(message_div)
-        messages.appendChild(date_elt)
-      }
-    } else {
-      if (message_type === 'call' || message_type === 'contact') {
-        message_div.querySelector('.message_wrapper').appendChild(date_elt)
-        messages.insertBefore(message_div, messages.firstChild)
-      } else {
-        messages.insertBefore(date_elt, messages.firstChild)
-        messages.insertBefore(message_div, messages.firstChild)
-      }
-    }
-  }
-
-  // Update informations if needed
-  if (message_type === 'data_transfer')
-    updateFileInteraction(message_div, message_object)
-  if (message_type === 'text' && message_direction === 'out')
+    // Update informations if needed
+    if (message_type === "data_transfer")
+        updateFileInteraction(message_div, message_object)
+    if (message_type === "text" && message_direction === "out")
     // Modify sent status if necessary
-    updateTextInteraction(message_div, delivery_status)
-  if (message_type === 'call')
-    updateCallInteraction(message_div, message_object)
-  if (message_type === 'contact')
-    updateContactInteraction(message_div, message_object)
+        updateTextInteraction(message_div, delivery_status)
+    if (message_type === "call")
+        updateCallInteraction(message_div, message_object)
+    if (message_type === "contact")
+        updateContactInteraction(message_div, message_object)
 
-  // Clean timestamps
-  updateTimestamps();
+    // Clean timestamps
+    updateTimestamps()
 }
 
-
 /**
- * Add a message to the buffer
+ * Add a message to the buffer.
+ *
+ * @param message_object message to be added
  */
+/* exported addMessage */
 function addMessage(message_object)
 {
-    var atEnd = messages.scrollTop >= messages.scrollHeight - messages.clientHeight - 5;
-    addOrUpdateMessage(message_object, true);
+    var atEnd = messages.scrollTop >= messages.scrollHeight - messages.clientHeight - 5
+    addOrUpdateMessage(message_object, true)
     if (atEnd) {
         var startTime = Date.now(),
             durTime = 250.,
             scrollStartHeight = messages.scrollHeight,
             scrollStart = messages.scrollTop,
-            scrollDiff = scrollStartHeight - messages.clientHeight - scrollStart;
-
+            scrollDiff = scrollStartHeight - messages.clientHeight - scrollStart
         function loop() {
             var time = Date.now() - startTime,
                 scrollHeight = messages.scrollHeight,
-                diff = messages.scrollTop - scrollStart; // If user scrolls up (diff > 0).
+                diff = messages.scrollTop - scrollStart // If user scrolls up (diff > 0).
 
             if (time >= durTime || scrollHeight != scrollStartHeight || diff < 0) {
                 if (diff >= 0) { // User scrolled up, don't autoscroll.
-                    messages.scrollTop = scrollHeight;
+                    messages.scrollTop = scrollHeight
                 }
-                return false;
+                return false
             } else {
-                messages.scrollTop = scrollStart + (scrollDiff * (time/durTime));
-                raf(loop);
+                messages.scrollTop = scrollStart + (scrollDiff * (time/durTime))
+                raf(loop)
             }
         }
-        raf(loop); // Start the animation loop
+
+
+        raf(loop) // Start the animation loop
     }
 }
 
 /**
  * Show the history in reverse order
  */
-function printHistoryPart () {
-  if(historyBuffer.length == 0) {
-      // nothing to print
-      return;
-  }
+function printHistoryPart() {
+    if(historyBuffer.length == 0 || historyBufferIndex === historyBuffer.length) {
+        // nothing to print
+        return
+    }
 
-  var previousScrollHeightMinusTop = messages.scrollHeight - messages.scrollTop;
+    var previousScrollHeightMinusTop = messages.scrollHeight - messages.scrollTop
 
-  // Show 10 messages
-  for (var i = 0; i < 10; ++i) {
-    addOrUpdateMessage(historyBuffer[historyBuffer.length - 1 - historyBufferIndex], true, false);
-    historyBufferIndex ++;
-    if (historyBufferIndex === historyBuffer.length)
-      break;
-  }
+    // Show 10 messages
+    for (var i = 0; i < 10; ++i) {
+        addOrUpdateMessage(historyBuffer[historyBuffer.length - 1 - historyBufferIndex], true, false)
+        historyBufferIndex ++
 
-  // Replace the scrollbar to the wanted position
-  messages.scrollTop = messages.scrollHeight - previousScrollHeightMinusTop;
+        if (historyBufferIndex === historyBuffer.length)
+            break
+    }
 
-  // Make a short pause to minimize ressources consumption
-  // show quickly the first hundred messages
-  if (historyBufferIndex !== historyBuffer.length)
-    setTimeout(printHistoryPart, (historyBufferIndex > 100) ? 100 : 1);
+    // Replace the scrollbar to the wanted position
+    messages.scrollTop = messages.scrollHeight - previousScrollHeightMinusTop
+
+    // Make a short pause to minimize ressources consumption
+    // show quickly the first hundred messages
+    if (historyBufferIndex !== historyBuffer.length)
+        setTimeout(printHistoryPart, (historyBufferIndex > 100) ? 100 : 1)
 }
 
 /**
  * Show the whole history in the chatview.
+ * @param messages_array array containing history to be printed
  */
+/* exported printHistory */
 function printHistory(messages_array)
 {
-    historyBuffer = messages_array;
-    historyBufferIndex = 0;
-    setTimeout(printHistoryPart, 0);
+    historyBuffer = messages_array
+    historyBufferIndex = 0
+    setTimeout(printHistoryPart, 0)
 }
 
 /**
- * Updated a message that was previously added with addMessage
+ * Update a message that was previously added with addMessage
+ * @param message_object message to be updated
  */
+/* exported updateMessage */
 function updateMessage(message_object)
 {
-    addOrUpdateMessage(message_object, false);
+    addOrUpdateMessage(message_object, false)
 }
 
 /**
@@ -1087,42 +1183,41 @@
  * set_sender_image object should contain the following keys:
  * - sender: the name of the sender
  * - sender_image: base64 png encoding of the sender image
+ *
+ * @param set_sender_image_object sender image object as previously described
  */
+/* exported setSenderImage */
 function setSenderImage(set_sender_image_object)
 {
-    var sender_contact_method = set_sender_image_object['sender_contact_method'],
-        sender_image = set_sender_image_object['sender_image'],
+    var sender_contact_method = set_sender_image_object["sender_contact_method"],
+        sender_image = set_sender_image_object["sender_image"],
         sender_image_id = "sender_image_" + sender_contact_method,
         currentSenderImage = document.getElementById("#" + sender_image_id), // Remove the currently set sender image
-        style;
+        style
 
     if (currentSenderImage) {
-        currentSenderImage.parentNode.removeChild(currentSenderImage);
+        currentSenderImage.parentNode.removeChild(currentSenderImage)
     }
 
     // Create a new style element
-    style = document.createElement('style');
+    style = document.createElement("style")
 
-    style.type = 'text/css';
-    style.id = sender_image_id;
-    style.innerHTML = '.' + sender_image_id + " {content: url(data:image/png;base64," + sender_image + ");height: 35px;width: 35px;}";
-    document.head.appendChild(style);
+    style.type = "text/css"
+    style.id = sender_image_id
+    style.innerHTML = "." + sender_image_id + " {content: url(data:image/png;base64," + sender_image + ");height: 35px;width: 35px;}"
+    document.head.appendChild(style)
 }
 
+/* exported clearSenderImages */
 function clearSenderImages()
 {
     var styles = document.head.querySelectorAll("style"),
-    i = styles.length;
+        i = styles.length
 
     while (i--){
-      document.head.removeChild(styles[i]);
+        document.head.removeChild(styles[i])
     }
 }
 
-function sendFile()
-{
-  window.prompt('SEND_FILE');
-}
-
 </script>
 </html>