chatview: fix scrollbar issues
Add a new wrapper function handling scrolling policy and use it
everywhere instead of doing code duplication. Remove dead code and
add meaningful comments.
Change-Id: I888377befdd939f3346629fb8c8df528c44140d2
Reviewed-by: Sebastien Blin <sebastien.blin@savoirfairelinux.com>
diff --git a/web/chatview.html b/web/chatview.html
index cd88dbf..fec9979 100644
--- a/web/chatview.html
+++ b/web/chatview.html
@@ -88,11 +88,14 @@
/* Constants used at several places*/
const messageBarPlaceHolder = "Type a message"
const avatar_size = 35
-var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame
-var displayLinksEnabled = false
+// scrollDetectionThresh represents the number of pixels a user can scroll
+// without disabling the automatic go-back-to-bottom when a new message is
+// received
+const scrollDetectionThresh = 200
+
+/* Buffers */
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 */
@@ -109,9 +112,25 @@
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 displayLinksEnabled = false
+var hoverBackButtonAllowed = true
var hasInvitation = false
+var isTemporary = false
+var isBanned = false
+
+/**
+ * Generic wrapper. Execute passed function keeping scroll position identical.
+ *
+ * @param func function to execute
+ * @param args parameters as array
+ */
+function exec_keeping_scroll_position(func, args) {
+ var atEnd = messages.scrollTop >= messages.scrollHeight - messages.clientHeight - scrollDetectionThresh
+ func(...args)
+ if (atEnd) {
+ messages.scrollTop = messages.scrollHeight
+ }
+}
/**
* Update common frame between conversations.
@@ -234,21 +253,17 @@
*/
/* exported grow_text_area */
function grow_text_area() {
- var is_at_bottom = messages.scrollTop === (messages.scrollHeight - messages.offsetHeight)
+ exec_keeping_scroll_position(function(){
+ 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
- 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
+ var msgbar_size = window.getComputedStyle(document.body).getPropertyValue("--messagebar-size")
+ var total_size = parseInt(msgbar_size) + parseInt(new_height) - parseInt(old_height)
- 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
- }
+ document.body.style.setProperty("--messagebar-size", total_size.toString() + "px")
+ }, [])
}
/**
@@ -521,54 +536,58 @@
*/
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) {
- if (child.className === baseNode.className &&
+ exec_keeping_scroll_position(function() {
+ for (var c = endIndex - 1 ; c >= 0 ; --c) {
+ const child = messages.children[c]
+ if (child.className.indexOf("timestamp") !== -1) {
+ if (child.className === baseNode.className &&
child.innerHTML === baseNode.innerHTML) {
- messages.removeChild(child)
- } else {
- break // A different timestamp is met, we can stop here.
+ messages.removeChild(child)
+ } else {
+ break // A different timestamp is met, we can stop here.
+ }
}
}
- }
+ }, [])
}
/**
* Update timestamps.
*/
function updateTimestamps() {
- 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) {
+ exec_keeping_scroll_position(function() {
+ 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) {
// Update timestamp
- child.innerHTML = getMessageTimestampText(child.getAttribute("message_timestamp"), true)
+ child.innerHTML = getMessageTimestampText(child.getAttribute("message_timestamp"), true)
- var desktop_margin = "25%"
- const height = document.body.clientHeight
- const width = document.body.clientWidth
- if (width <= 1920 || height <= 1080) {
- desktop_margin = "15%"
+ var desktop_margin = "25%"
+ const height = document.body.clientHeight
+ const width = document.body.clientWidth
+ if (width <= 1920 || height <= 1080) {
+ desktop_margin = "15%"
+ }
+ if (width <= 1000 || height <= 480) {
+ 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})`
+ }
+ // Remove previous elements with the same formatted timestamp
+ cleanPreviousTimestamps(child, c)
}
- if (width <= 1000 || height <= 480) {
- 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})`
- }
- // Remove previous elements with the same formatted timestamp
- cleanPreviousTimestamps(child, c)
}
}
- }
+ }, [])
}
/**
@@ -595,15 +614,17 @@
* @param message_object
*/
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
+ exec_keeping_scroll_position(function() {
+ 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")
- } else
- progress_bar.setAttribute("style", "display: none")
+ }, [])
}
/**
@@ -684,22 +705,24 @@
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) {
+ exec_keeping_scroll_position(function() {
+ 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)
- }
+ 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
}
@@ -782,7 +805,7 @@
* @param message_id
* @param link to show
* @param ytid if it's a youtube video
- * @param
+ * @param noerror
*/
function mediaInteraction(message_id, link, ytid, noerror) {
/* TODO promise?
@@ -1051,7 +1074,7 @@
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>
+ // Remove last timestamp if it's the same
if (messages.getElementsByClassName(".timestamp"))
cleanPreviousTimestamps(date_elt, messages.children.length)
@@ -1091,64 +1114,50 @@
}
/**
- * Add a message to the buffer.
+ * Wrapper for addOrUpdateMessage.
+ *
+ * This function adds or updates a message and makes sure the scrollbar position
+ * is refreshed correctly.
*
* @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)
- if (atEnd) {
- var startTime = Date.now(),
- durTime = 250.,
- scrollStartHeight = messages.scrollHeight,
- scrollStart = messages.scrollTop,
- 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).
-
- if (time >= durTime || scrollHeight != scrollStartHeight || diff < 0) {
- if (diff >= 0) { // User scrolled up, don't autoscroll.
- messages.scrollTop = scrollHeight
- }
- return false
- } else {
- messages.scrollTop = scrollStart + (scrollDiff * (time/durTime))
- raf(loop)
- }
- }
-
-
- raf(loop) // Start the animation loop
- }
+ exec_keeping_scroll_position(addOrUpdateMessage, [message_object, true])
}
/**
- * Show the history in reverse order
+ * Update a message that was previously added with addMessage
+ * @param message_object message to be updated
*/
-function printHistoryPart() {
+/* exported updateMessage */
+function updateMessage(message_object)
+{
+ exec_keeping_scroll_position(addOrUpdateMessage, [message_object, false])
+}
+
+/**
+ * This function displays the history in reverse order and makes sure the
+ * scrollbar position is refreshed correctly.
+ */
+function printHistoryPart()
+{
if(historyBuffer.length == 0 || historyBufferIndex === historyBuffer.length) {
// nothing to print
return
}
- var previousScrollHeightMinusTop = messages.scrollHeight - messages.scrollTop
+ exec_keeping_scroll_position(function() {
+ // Show 10 messages
+ for (var i = 0; i < 10; ++i) {
+ addOrUpdateMessage(historyBuffer[historyBuffer.length - 1 - historyBufferIndex], true, false)
+ historyBufferIndex ++
- // Show 10 messages
- for (var i = 0; i < 10; ++i) {
- addOrUpdateMessage(historyBuffer[historyBuffer.length - 1 - historyBufferIndex], true, false)
- historyBufferIndex ++
-
- if (historyBufferIndex === historyBuffer.length)
- break
- }
-
- // 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
@@ -1169,16 +1178,6 @@
}
/**
- * 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)
-}
-
-/**
* Sets the image for a given sender
* set_sender_image object should contain the following keys:
* - sender: the name of the sender