summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/im/messages/bubbles/inline.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/im/messages/bubbles/inline.js')
-rw-r--r--comm/mail/components/im/messages/bubbles/inline.js330
1 files changed, 330 insertions, 0 deletions
diff --git a/comm/mail/components/im/messages/bubbles/inline.js b/comm/mail/components/im/messages/bubbles/inline.js
new file mode 100644
index 0000000000..11bdec3f29
--- /dev/null
+++ b/comm/mail/components/im/messages/bubbles/inline.js
@@ -0,0 +1,330 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// See chat/content/conversation-browser.js _exposeMethodsToContent
+/* globals convScrollEnabled, scrollToElement */
+
+/* [pseudo_color, pseudo_background, bubble_borders] */
+const elements_lightness = [
+ [75, 94, 80],
+ [75, 94, 80],
+ [70, 93, 75],
+ [65, 92, 70],
+ [55, 90, 65],
+ [48, 90, 60],
+ [44, 86, 50],
+ [44, 88, 60],
+ [45, 88, 70],
+ [45, 90, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [45, 92, 70],
+ [60, 92, 70],
+ [70, 93, 75],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+ [75, 94, 80],
+];
+
+const bubble_background = "hsl(#, 100%, 97%)";
+const bubble_borders = "hsl(#, 100%, #%)";
+const pseudo_color = "hsl(#, 100%, #%)";
+const pseudo_background = "hsl(#, 100%, #%)";
+
+var alternating = null;
+
+function setColors(target) {
+ var senderColor = target.getAttribute("data-senderColor");
+
+ if (!senderColor) {
+ return;
+ }
+
+ var regexp =
+ /color:\s*hsl\(\s*(\d{1,3})\s*,\s*\d{1,3}\%\s*,\s*\d{1,3}\%\s*\)/;
+ var parsed = regexp.exec(senderColor);
+
+ if (!parsed) {
+ return;
+ }
+
+ var senderHue = (Math.round(parsed[1] / 10) * 10) % 360;
+ var lightness = elements_lightness[senderHue / 10];
+
+ target.style.backgroundColor = bubble_background.replace("#", senderHue);
+ target.style.borderColor = bubble_borders
+ .replace("#", senderHue)
+ .replace("#", lightness[2]);
+
+ var pseudo = target.getElementsByClassName("pseudo")[0];
+ pseudo.style.color = pseudo_color
+ .replace("#", senderHue)
+ .replace("#", lightness[0]);
+ pseudo.style.backgroundColor = pseudo_background
+ .replace("#", senderHue)
+ .replace("#", lightness[1]);
+
+ var div_indicator = target.getElementsByClassName("indicator")[0];
+ var imageURL = "url('Bitmaps/indicator_" + senderHue;
+ if (target.classList.contains("incoming")) {
+ // getComputedStyle is prohibitively expensive, and we need it only to
+ // know if we are using an alternating variant, so we cache the result.
+ if (alternating === null) {
+ alternating = document.defaultView
+ .getComputedStyle(div_indicator)
+ .backgroundImage.endsWith('_alt.png")')
+ ? "_alt"
+ : "";
+ }
+ imageURL += alternating;
+ }
+ div_indicator.style.backgroundImage = imageURL + ".png')";
+}
+
+function prettyPrintTime(aValue, aNoSeconds) {
+ if (aValue < 60 && aNoSeconds) {
+ return "";
+ }
+
+ if (aNoSeconds) {
+ aValue -= aValue % 60;
+ }
+
+ let valuesAndUnits = window.convertTimeUnits(aValue);
+ if (!valuesAndUnits[2]) {
+ valuesAndUnits.splice(2, 2);
+ }
+ return valuesAndUnits.join(" ");
+}
+
+// The "shadow" constant is the minimum acceptable margin-bottom for a bubble
+// with a shadow, and the minimum spacing between the bubbles of two messages
+// arriving in the same second. It should match the value of margin-bottom and
+// box-shadow-bottom for the "bubble" class.
+const shadow = 3;
+const coef = 3;
+const timebeforetextdisplay = 5 * 60;
+const kRulerMarginTop = 11;
+
+const kMsPerMinute = 60 * 1000;
+const kMsPerHour = 60 * kMsPerMinute;
+const kMsPerDay = 24 * kMsPerHour;
+
+function computeSpace(aInterval) {
+ return Math.round(coef * Math.log(aInterval + 1));
+}
+
+var lastMessageTimeout;
+var lastMessageTimeoutTime = -1;
+
+/* This function takes care of updating the amount of whitespace
+ * between the last message and the bottom of the conversation area.
+ * When the last message is more than timebeforetextdisplay old, we display
+ * the time in text. To avoid blinking Mac scrollbar and visual distractions
+ * for some very sensitive users, we update the whitespace only when a new
+ * message is displayed or when the user switches between tabs. While the
+ * conversation is visible, this function is called by timers, but we will
+ * only update the time displayed in text (this behavior is obtained by
+ * setting the aUpdateTextOnly parameter to true; otherwise it is omitted).
+ */
+function handleLastMessage(aUpdateTextOnly) {
+ if (window.messageInsertPending) {
+ return;
+ }
+
+ var intervalInMs = Date.now() - lastMsgTime * 1000;
+ var interval = Math.round(intervalInMs / 1000);
+ var p = document.getElementById("lastMessage");
+ var margin;
+ if (!aUpdateTextOnly) {
+ // Impose a minimum to ensure the last bubble doesn't touch the editbox.
+ margin = computeSpace(Math.max(intervalInMs, 5000) / 1000);
+ }
+ var text = "";
+ if (interval >= timebeforetextdisplay) {
+ if (!aUpdateTextOnly) {
+ p.style.lineHeight = margin + shadow + "px";
+ }
+ p.setAttribute("class", "interval");
+ text = prettyPrintTime(interval, true);
+ margin = 0;
+ }
+ p.textContent = text;
+ if (!aUpdateTextOnly) {
+ p.style.marginTop = margin - shadow + "px";
+ if (convScrollEnabled()) {
+ scrollToElement(p);
+ }
+ }
+
+ var next = timebeforetextdisplay * 1000 - intervalInMs;
+ if (next <= 0) {
+ if (intervalInMs > kMsPerDay) {
+ next = kMsPerHour - (intervalInMs % kMsPerHour);
+ } else {
+ next = kMsPerMinute - (intervalInMs % kMsPerMinute);
+ }
+ aUpdateTextOnly = true;
+ }
+
+ // The setTimeout callbacks are frequently called a few ms early,
+ // but our code prefers being called a little late, so add 20ms.
+ lastMessageTimeoutTime = next + 20;
+ lastMessageTimeout = setTimeout(
+ handleLastMessage,
+ lastMessageTimeoutTime,
+ aUpdateTextOnly
+ );
+}
+
+var lastMsgTime = 0;
+function updateLastMsgTime(aMsgTime) {
+ if (aMsgTime > lastMsgTime) {
+ lastMsgTime = aMsgTime;
+ }
+
+ if (lastMsgTime && lastMessageTimeoutTime != 0 && !document.hidden) {
+ clearTimeout(lastMessageTimeout);
+ setTimeout(handleLastMessage, 0);
+ lastMessageTimeoutTime = 0;
+ }
+}
+
+function visibilityChanged() {
+ if (document.hidden) {
+ clearTimeout(lastMessageTimeout);
+ lastMessageTimeoutTime = -1;
+ } else if (lastMsgTime) {
+ handleLastMessage();
+ }
+}
+
+function checkNewText(target) {
+ var nicks = target.getElementsByClassName("ib-nick");
+ for (var i = 0; i < nicks.length; ++i) {
+ var nick = nicks[i];
+ if (nick.hasAttribute("data-left")) {
+ continue;
+ }
+ var hue = nick.getAttribute("data-nickColor");
+ var senderHue = (Math.round(hue / 10) * 10) % 360;
+ var lightness = elements_lightness[senderHue / 10];
+ nick.style.backgroundColor = pseudo_background
+ .replace("#", senderHue)
+ .replace("#", lightness[1]);
+ nick.style.color = pseudo_color
+ .replace("#", senderHue)
+ .replace("#", lightness[0]);
+ nick.style.borderColor = bubble_borders
+ .replace("#", senderHue)
+ .replace("#", lightness[2]);
+ }
+
+ var msgTime = null;
+ if (target._originalMsg) {
+ msgTime = target._originalMsg.time;
+ }
+ if (target.tagName == "DIV" && target.classList.contains("bubble")) {
+ setColors(target);
+
+ var prev = target.previousElementSibling;
+ var shouldSetUnreadRuler = prev && prev.id && prev.id == "unread-ruler";
+ var shouldSetSessionRuler =
+ prev && prev.className && prev.className == "sessionstart-ruler";
+ // We need an extra pixel of margin at the top to make the margins appear
+ // to be of equal size, since the preceding bubble will have a shadow.
+ var rulerMarginBottom = kRulerMarginTop - 1;
+
+ if (lastMsgTime && msgTime >= lastMsgTime) {
+ var interval = msgTime - lastMsgTime;
+ var margin = computeSpace(interval);
+ let isTimetext = interval >= timebeforetextdisplay;
+ if (isTimetext) {
+ let p = document.createElement("p");
+ p.className = "interval";
+ if (shouldSetSessionRuler) {
+ // Hide the hr and style the time text accordingly instead.
+ prev.classList.remove("sessionstart-ruler");
+ prev.style.border = "none";
+ p.classList.add("sessionstart-ruler");
+ margin += 6;
+ prev = p;
+ }
+ p.style.lineHeight = margin + shadow + "px";
+ p.style.marginTop = -shadow + "px";
+ p.textContent = prettyPrintTime(interval);
+ target.parentNode.insertBefore(p, target);
+ margin = 0;
+ }
+ target.style.marginTop = margin + "px";
+ if (shouldSetUnreadRuler || shouldSetSessionRuler) {
+ if (margin > rulerMarginBottom) {
+ // Set the unread ruler margin so it is constant after margin collapse.
+ // See https://developer.mozilla.org/en/CSS/margin_collapsing
+ rulerMarginBottom -= margin;
+ }
+ if (isTimetext && shouldSetUnreadRuler) {
+ // If a text display follows, use the minimum bubble margin after the
+ // ruler, taking account of the absence of a shadow on the ruler.
+ rulerMarginBottom = shadow - 1;
+ }
+ }
+ }
+ if (shouldSetUnreadRuler || shouldSetSessionRuler) {
+ prev.style.marginBottom = rulerMarginBottom + "px";
+ prev.style.marginTop = kRulerMarginTop + "px";
+ }
+ } else if (target.tagName == "P" && target.className == "event") {
+ let parent = target.parentNode;
+ // We need to start a group with this element if there are at least 4
+ // system messages and they aren't already grouped.
+ if (!parent?.grouped && parent?.querySelector("p.event:nth-of-type(4)")) {
+ let p = document.createElement("p");
+ p.className = "eventToggle";
+ p.addEventListener("click", event =>
+ event.target.parentNode.classList.toggle("hide-children")
+ );
+ parent.insertBefore(p, parent.querySelector("p.event:nth-of-type(2)"));
+ parent.classList.add("hide-children");
+ parent.grouped = true;
+ }
+ }
+
+ if (msgTime) {
+ updateLastMsgTime(msgTime);
+ }
+}
+
+new MutationObserver(function (aMutations) {
+ for (let mutation of aMutations) {
+ for (let node of mutation.addedNodes) {
+ if (node instanceof HTMLElement) {
+ checkNewText(node);
+ }
+ }
+ }
+}).observe(document.getElementById("ibcontent"), {
+ childList: true,
+ subtree: true,
+});
+
+document.addEventListener("visibilitychange", visibilityChanged);