From fa2bffd618f1b5336cb9ba0f4e0dd74780f8edc5 Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Thu, 19 May 2016 12:16:19 -0400
Subject: [PATCH] FIX: Dock the timeline if you scroll down too much

---
 .../components/scrolling-post-stream.js.es6   |  4 +-
 .../discourse/components/site-header.js.es6   | 52 +++++++------------
 .../components/topic-timeline.js.es6          | 39 +++++++++++---
 .../discourse/mixins/docking.js.es6           | 25 +++++++++
 .../javascripts/discourse/templates/topic.hbs | 12 ++---
 .../discourse/widgets/topic-timeline.js.es6   | 19 +++++++
 .../stylesheets/desktop/topic-timeline.scss   |  7 ++-
 7 files changed, 109 insertions(+), 49 deletions(-)
 create mode 100644 app/assets/javascripts/discourse/mixins/docking.js.es6

diff --git a/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6 b/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
index 89f77192b..cfbcb5ffa 100644
--- a/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
+++ b/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
@@ -30,7 +30,7 @@ export default MountWidget.extend({
   _currentPost: -1,
   _currentVisible: null,
 
-  args: Ember.computed(function() {
+  buildArgs() {
     return this.getProperties('posts',
                               'canCreatePost',
                               'multiSelect',
@@ -38,7 +38,7 @@ export default MountWidget.extend({
                               'selectedQuery',
                               'selectedPostsCount',
                               'searchService');
-  }).volatile(),
+  },
 
   beforePatch() {
     const $body = $(document);
diff --git a/app/assets/javascripts/discourse/components/site-header.js.es6 b/app/assets/javascripts/discourse/components/site-header.js.es6
index d8e87bb7d..0cac49b19 100644
--- a/app/assets/javascripts/discourse/components/site-header.js.es6
+++ b/app/assets/javascripts/discourse/components/site-header.js.es6
@@ -1,5 +1,6 @@
 import MountWidget from 'discourse/components/mount-widget';
 import { observes } from 'ember-addons/ember-computed-decorators';
+import Docking from 'discourse/mixins/docking';
 
 const _flagProperties = [];
 function addFlagProperty(prop) {
@@ -8,45 +9,37 @@ function addFlagProperty(prop) {
 
 const PANEL_BODY_MARGIN = 30;
 
-const SiteHeaderComponent = MountWidget.extend({
+const SiteHeaderComponent = MountWidget.extend(Docking, {
   widget: 'header',
   docAt: null,
   dockedHeader: null,
   _topic: null,
 
-  // profileWidget: true,
-  // classNameBindings: ['editingTopic'],
-
   @observes('currentUser.unread_notifications', 'currentUser.unread_private_messages')
   _notificationsChanged() {
     this.queueRerender();
   },
 
-  examineDockHeader() {
+  dockCheck(info) {
+    if (this.docAt === null) {
+      const outlet = $('#main-outlet');
+      if (!(outlet && outlet.length === 1)) return;
+      this.docAt = outlet.offset().top;
+    }
+
     const $body = $('body');
-
-    // Check the dock after the current run loop. While rendering,
-    // it's much slower to calculate `outlet.offset()`
-    Ember.run.next(() => {
-      if (this.docAt === null) {
-        const outlet = $('#main-outlet');
-        if (!(outlet && outlet.length === 1)) return;
-        this.docAt = outlet.offset().top;
+    const offset = info.offset();
+    if (offset >= this.docAt) {
+      if (!this.dockedHeader) {
+        $body.addClass('docked');
+        this.dockedHeader = true;
       }
-
-      const offset = window.pageYOffset || $('html').scrollTop();
-      if (offset >= this.docAt) {
-        if (!this.dockedHeader) {
-          $body.addClass('docked');
-          this.dockedHeader = true;
-        }
-      } else {
-        if (this.dockedHeader) {
-          $body.removeClass('docked');
-          this.dockedHeader = false;
-        }
+    } else {
+      if (this.dockedHeader) {
+        $body.removeClass('docked');
+        this.dockedHeader = false;
       }
-    });
+    }
   },
 
   setTopic(topic) {
@@ -56,8 +49,6 @@ const SiteHeaderComponent = MountWidget.extend({
 
   didInsertElement() {
     this._super();
-    $(window).bind('scroll.discourse-dock', () => this.examineDockHeader());
-    $(document).bind('touchmove.discourse-dock', () => this.examineDockHeader());
     $(window).on('resize.discourse-menu-panel', () => this.afterRender());
 
     this.appEvents.on('header:show-topic', topic => this.setTopic(topic));
@@ -72,16 +63,13 @@ const SiteHeaderComponent = MountWidget.extend({
         this.eventDispatched('dom:clean', 'header');
       }
     });
-
-    this.examineDockHeader();
   },
 
   willDestroyElement() {
     this._super();
-    $(window).unbind('scroll.discourse-dock');
-    $(document).unbind('touchmove.discourse-dock');
     $('body').off('keydown.header');
     this.appEvents.off('notifications:changed');
+    this.appEvents.off('header:keyboard-trigger');
     $(window).off('resize.discourse-menu-panel');
 
     this.appEvents.off('header:show-topic');
diff --git a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 b/app/assets/javascripts/discourse/components/topic-timeline.js.es6
index bb96c4c63..1a5dcbbdd 100644
--- a/app/assets/javascripts/discourse/components/topic-timeline.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-timeline.js.es6
@@ -1,16 +1,43 @@
 import MountWidget from 'discourse/components/mount-widget';
-import computed from 'ember-addons/ember-computed-decorators';
+import Docking from 'discourse/mixins/docking';
 
-export default MountWidget.extend({
-  widget: 'topic-timeline',
+export default MountWidget.extend(Docking, {
+  widget: 'topic-timeline-container',
+  dockAt: null,
 
-  @computed('topic')
-  args(topic) {
-    return { topic, topicTrackingState: this.topicTrackingState };
+  buildArgs() {
+    return { topic: this.get('topic'),
+             topicTrackingState: this.topicTrackingState,
+             dockAt: this.dockAt };
+  },
+
+  dockCheck(info) {
+    const topicBottom = $('#topic-bottom').offset().top;
+    const $timeline = this.$('.timeline-container');
+    const timelineHeight = $timeline.height();
+
+    const tTop = 140;
+
+    const prev = this.dockAt;
+    const pos = tTop + info.offset() + timelineHeight;
+    if (pos > topicBottom) {
+      this.dockAt = topicBottom - timelineHeight - $timeline.offsetParent().offset().top;
+    } else {
+      this.dockAt = null;
+    }
+
+    if (this.dockAt !== prev) {
+      this.queueRerender();
+    }
   },
 
   didInsertElement() {
     this._super();
     this.dispatch('topic:current-post-changed', 'timeline-scrollarea');
+  },
+
+  willDestroyElement() {
+    this._super();
+    this.appEvents.off('topic:current-post-changed');
   }
 });
diff --git a/app/assets/javascripts/discourse/mixins/docking.js.es6 b/app/assets/javascripts/discourse/mixins/docking.js.es6
new file mode 100644
index 000000000..a70bcdee5
--- /dev/null
+++ b/app/assets/javascripts/discourse/mixins/docking.js.es6
@@ -0,0 +1,25 @@
+const helper = {
+  offset: () => window.pageYOffset || $('html').scrollTop()
+};
+
+export default Ember.Mixin.create({
+  _dockHandler: null,
+
+  didInsertElement() {
+    this._super();
+
+    // Check the dock after the current run loop since reading sizes is slow
+    this._dockHandler = () => Ember.run.next(() => this.dockCheck(helper));
+
+    $(window).bind('scroll.discourse-dock', this._dockHandler);
+    $(document).bind('touchmove.discourse-dock', this._dockHandler);
+
+    this._dockHandler();
+  },
+
+  willDestroyElement() {
+    this._super();
+    $(window).unbind('scroll.discourse-dock', this._dockHandler);
+    $(document).unbind('touchmove.discourse-dock', this._dockHandler);
+  }
+});
diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs
index f1e69848a..e7457fb8a 100644
--- a/app/assets/javascripts/discourse/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/templates/topic.hbs
@@ -72,13 +72,11 @@
         <div class="posts-wrapper">
 
           {{#if showTimeline}}
-            <div class='fixed-gutter'>
-              {{topic-timeline topic=model
-                               jumpTop="jumpTop"
-                               jumpToPost="jumpToPost"
-                               jumpBottom="jumpBottom"
-                               replyToPost="replyToPost"}}
-            </div>
+            {{topic-timeline topic=model
+                             jumpTop="jumpTop"
+                             jumpToPost="jumpToPost"
+                             jumpBottom="jumpBottom"
+                             replyToPost="replyToPost"}}
           {{else}}
             {{topic-progress topic=model
                              jumpTop="jumpTop"
diff --git a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
index b1df89fc2..275deb539 100644
--- a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
@@ -169,6 +169,25 @@ createWidget('timeline-scrollarea', {
   }
 });
 
+createWidget('topic-timeline-container', {
+  tagName: 'div.timeline-container',
+  buildClasses(attrs) {
+    if (attrs.dockAt) { return 'timeline-docked'; }
+  },
+
+  buildAttributes(attrs) {
+    if (attrs.dockAt) {
+      return { style: `top: ${attrs.dockAt}px` };
+    };
+
+    return { style: 'top: 140px' };
+  },
+
+  html(attrs) {
+    return this.attach('topic-timeline', attrs);
+  }
+});
+
 export default createWidget('topic-timeline', {
   tagName: 'div.topic-timeline',
 
diff --git a/app/assets/stylesheets/desktop/topic-timeline.scss b/app/assets/stylesheets/desktop/topic-timeline.scss
index 5cd77f5d8..a90dc9127 100644
--- a/app/assets/stylesheets/desktop/topic-timeline.scss
+++ b/app/assets/stylesheets/desktop/topic-timeline.scss
@@ -2,13 +2,16 @@
   width: 900px;
 }
 
-.fixed-gutter {
+.timeline-container {
   width: 100%;
   box-sizing: border-box;
   z-index: 1;
   margin-left: 757px;
   position: fixed;
-  top: 140px;
+
+  &.timeline-docked {
+    position: absolute;
+  }
 
   .topic-timeline {
     margin-left: 3em;