From f34855573ef203182994eac6d9ecaa027225dd48 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Thu, 7 Feb 2019 16:24:26 +0000
Subject: [PATCH 1/6] replace ratelimitedfunc with lodash impl

---
 src/ratelimitedfunc.js | 58 ++++++++++++------------------------------
 1 file changed, 16 insertions(+), 42 deletions(-)

diff --git a/src/ratelimitedfunc.js b/src/ratelimitedfunc.js
index 20f6db79b8..1f15f11d91 100644
--- a/src/ratelimitedfunc.js
+++ b/src/ratelimitedfunc.js
@@ -20,54 +20,28 @@ limitations under the License.
  * to update the interface once for all of them.
  *
  * Note that the function must not take arguments, since the args
- * could be different for each invocarion of the function.
+ * could be different for each invocation of the function.
  *
  * The returned function has a 'cancelPendingCall' property which can be called
  * on unmount or similar to cancel any pending update.
  */
-module.exports = function(f, minIntervalMs) {
-    this.lastCall = 0;
-    this.scheduledCall = undefined;
 
-    const self = this;
-    const wrapper = function() {
-        const now = Date.now();
+import { throttle } from "lodash";
 
-        if (self.lastCall < now - minIntervalMs) {
-            f.apply(this);
-            // get the time again now the function has finished, so if it
-            // took longer than the delay time to execute, it doesn't
-            // immediately become eligible to run again.
-            self.lastCall = Date.now();
-        } else if (self.scheduledCall === undefined) {
-            self.scheduledCall = setTimeout(
-                () => {
-                    self.scheduledCall = undefined;
-                    f.apply(this);
-                    // get time again as per above
-                    self.lastCall = Date.now();
-                },
-                (self.lastCall + minIntervalMs) - now,
-            );
-        }
+export default function ratelimitedfunc(fn, time) {
+    const throttledFn = throttle(fn, time, {
+        leading: true,
+        trailing: true,
+    });
+    const _bind = throttledFn.bind;
+    throttledFn.bind = function() {
+        const boundFn = _bind.apply(throttledFn, arguments);
+        boundFn.cancelPendingCall = throttledFn.cancelPendingCall;
+        return boundFn;
     };
 
-    // add the cancelPendingCall property
-    wrapper.cancelPendingCall = function() {
-        if (self.scheduledCall) {
-            clearTimeout(self.scheduledCall);
-            self.scheduledCall = undefined;
-        }
+    throttledFn.cancelPendingCall = function() {
+        throttledFn.cancel();
     };
-
-    // make sure that cancelPendingCall is copied when react rebinds the
-    // wrapper
-    const _bind = wrapper.bind;
-    wrapper.bind = function() {
-        const rebound = _bind.apply(this, arguments);
-        rebound.cancelPendingCall = wrapper.cancelPendingCall;
-        return rebound;
-    };
-
-    return wrapper;
-};
+    return throttledFn;
+}

From d2dd1bae135830f144f962a97fa246cc3ca3b260 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Thu, 7 Feb 2019 16:31:57 +0000
Subject: [PATCH 2/6] run search after you've stopped typing for 200ms instead
 of every 500ms

---
 src/components/structures/SearchBox.js | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js
index fbcd9a7279..2f777c1163 100644
--- a/src/components/structures/SearchBox.js
+++ b/src/components/structures/SearchBox.js
@@ -21,7 +21,7 @@ import { _t } from '../../languageHandler';
 import { KeyCode } from '../../Keyboard';
 import sdk from '../../index';
 import dis from '../../dispatcher';
-import rate_limited_func from '../../ratelimitedfunc';
+import { debounce } from 'lodash';
 import AccessibleButton from '../../components/views/elements/AccessibleButton';
 
 module.exports = React.createClass({
@@ -67,12 +67,9 @@ module.exports = React.createClass({
         this.onSearch();
     },
 
-    onSearch: new rate_limited_func(
-        function() {
-            this.props.onSearch(this.refs.search.value);
-        },
-        500,
-    ),
+    onSearch: debounce(function() {
+        this.props.onSearch(this.refs.search.value);
+    }, 200, {trailing: true}),
 
     _onKeyDown: function(ev) {
         switch (ev.keyCode) {

From b6aa72da55e2db5d0532dd8f11edd277430d0863 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Mon, 11 Feb 2019 15:40:17 +0100
Subject: [PATCH 3/6] update RoomTile notificationCount through props so
 updates are consistent

---
 src/components/structures/RoomSubList.js |  1 +
 src/components/views/rooms/RoomTile.js   | 11 +----------
 2 files changed, 2 insertions(+), 10 deletions(-)

diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 4644d19f61..ca2be85b35 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -145,6 +145,7 @@ const RoomSubList = React.createClass({
                 collapsed={this.props.collapsed || false}
                 unread={Unread.doesRoomHaveUnreadMessages(room)}
                 highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
+                notificationCount={room.getUnreadNotificationCount()}
                 isInvite={this.props.isInvite}
                 refreshSubList={this._updateSubListCount}
                 incomingCall={null}
diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js
index ed214812b5..f9e9d64b9e 100644
--- a/src/components/views/rooms/RoomTile.js
+++ b/src/components/views/rooms/RoomTile.js
@@ -108,13 +108,6 @@ module.exports = React.createClass({
         return statusUser._unstable_statusMessage;
     },
 
-    onRoomTimeline: function(ev, room) {
-        if (room !== this.props.room) return;
-        this.setState({
-            notificationCount: this.props.room.getUnreadNotificationCount(),
-        });
-    },
-
     onRoomName: function(room) {
         if (room !== this.props.room) return;
         this.setState({
@@ -159,7 +152,6 @@ module.exports = React.createClass({
 
     componentWillMount: function() {
         MatrixClientPeg.get().on("accountData", this.onAccountData);
-        MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
         MatrixClientPeg.get().on("Room.name", this.onRoomName);
         ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
         this.dispatcherRef = dis.register(this.onAction);
@@ -179,7 +171,6 @@ module.exports = React.createClass({
         const cli = MatrixClientPeg.get();
         if (cli) {
             MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
-            MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
             MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
         }
         ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
@@ -306,7 +297,7 @@ module.exports = React.createClass({
 
     render: function() {
         const isInvite = this.props.room.getMyMembership() === "invite";
-        const notificationCount = this.state.notificationCount;
+        const notificationCount = this.props.notificationCount;
         // var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
 
         const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();

From d069f4a89a4057d5107d1b2452068426d51eefc6 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Mon, 11 Feb 2019 15:41:32 +0100
Subject: [PATCH 4/6] not the cause of the bug, but this seems wrong

---
 src/Unread.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Unread.js b/src/Unread.js
index 55e60f2a9a..9514ec821b 100644
--- a/src/Unread.js
+++ b/src/Unread.js
@@ -32,7 +32,7 @@ module.exports = {
             return false;
         } else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
             return false;
-        } else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
+        } else if (ev.getType() == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
             return false;
         }
         const EventTile = sdk.getComponent('rooms.EventTile');

From fbc0bbfb6f7e80639c9d5288df632ead62938244 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Mon, 11 Feb 2019 16:17:15 +0100
Subject: [PATCH 5/6] clear hover flag after 1000ms of not moving the mouse

as a fix for #8184
---
 src/components/views/rooms/RoomList.js | 30 +++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index 56eb4b713d..1d207835bc 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -17,6 +17,7 @@ limitations under the License.
 
 'use strict';
 import SettingsStore from "../../../settings/SettingsStore";
+import Timer from "../../../utils/Timer";
 
 const React = require("react");
 const ReactDOM = require("react-dom");
@@ -41,6 +42,7 @@ import {Resizer} from '../../../resizer';
 import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
 const HIDE_CONFERENCE_CHANS = true;
 const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
+const HOVER_MOVE_TIMEOUT = 1000;
 
 function labelForTagName(tagName) {
     if (tagName.startsWith('u.')) return tagName.slice(2);
@@ -73,6 +75,7 @@ module.exports = React.createClass({
 
     getInitialState: function() {
 
+        this._hoverClearTimer = null;
         this._subListRefs = {
             // key => RoomSubList ref
         };
@@ -357,11 +360,32 @@ module.exports = React.createClass({
         this.forceUpdate();
     },
 
-    onMouseEnter: function(ev) {
-        this.setState({hover: true});
+    onMouseMove: async function(ev) {
+        if (!this._hoverClearTimer) {
+            this.setState({hover: true});
+            this._hoverClearTimer = new Timer(HOVER_MOVE_TIMEOUT);
+            this._hoverClearTimer.start();
+            let finished = true;
+            try {
+                await this._hoverClearTimer.finished();
+            } catch (err) {
+                finished = false;
+            }
+            this._hoverClearTimer = null;
+            if (finished) {
+                this.setState({hover: false});
+                this._delayedRefreshRoomList();
+            }
+        } else {
+            this._hoverClearTimer.restart();
+        }
     },
 
     onMouseLeave: function(ev) {
+        if (this._hoverClearTimer) {
+            this._hoverClearTimer.abort();
+            this._hoverClearTimer = null;
+        }
         this.setState({hover: false});
 
         // Refresh the room list just in case the user missed something.
@@ -774,7 +798,7 @@ module.exports = React.createClass({
 
         return (
             <div ref={this._collectResizeContainer} className="mx_RoomList"
-                 onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
+                 onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave}>
                 { subListComponents }
             </div>
         );

From ab6535b1353b18f7c03bc141e74aed8dd956fe3d Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Mon, 11 Feb 2019 16:27:29 +0100
Subject: [PATCH 6/6] lint

---
 src/components/views/rooms/RoomList.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index 1d207835bc..227dd318ed 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -98,7 +98,7 @@ module.exports = React.createClass({
             // update overflow indicators
             this._checkSubListsOverflow();
             // don't store height for collapsed sublists
-            if(!this.collapsedState[key]) {
+            if (!this.collapsedState[key]) {
                 this.subListSizes[key] = size;
                 window.localStorage.setItem("mx_roomlist_sizes",
                     JSON.stringify(this.subListSizes));