diff --git a/js/src/forum/components/PostStream.js b/js/src/forum/components/PostStream.js index 3ba97ef56..c2e667c0e 100644 --- a/js/src/forum/components/PostStream.js +++ b/js/src/forum/components/PostStream.js @@ -206,7 +206,7 @@ export default class PostStream extends Component { const threeQuartersVisible = visibleTop / height < 0.75; const coversQuarterOfViewport = (height - visibleTop) / viewportHeight > 0.25; if (index === undefined && (threeQuartersVisible || coversQuarterOfViewport)) { - index = parseFloat($this.data('index')) + visibleTop / height; + index = parseFloat($this.data('index')) + (visibleTop / height) * (1 / 0.75); // If this item has a time associated with it, then set the // scrollbar's current period to a formatted version of this time. const time = $this.data('time'); @@ -343,12 +343,12 @@ export default class PostStream extends Component { } return Promise.all([$container.promise(), this.state.loadPromise]).then(() => { - const index = $item.data('index'); + this.state.index = $item.data('index'); + this.updateScrubber(); m.redraw(true); - const scroll = index == 0 ? 0 : $(`.PostStream-item[data-index=${$item.data('index')}]`).offset().top - this.getMarginTop(); + const scroll = this.state.index == 0 ? 0 : $(`.PostStream-item[data-index=${$item.data('index')}]`).offset().top - this.getMarginTop(); $(window).scrollTop(scroll); this.calculatePosition(); - this.updateScrubber(); this.state.paused = false; m.redraw(); }); diff --git a/js/src/forum/components/PostStreamScrubber.js b/js/src/forum/components/PostStreamScrubber.js index 99ffad322..cc7a2a04d 100644 --- a/js/src/forum/components/PostStreamScrubber.js +++ b/js/src/forum/components/PostStreamScrubber.js @@ -17,21 +17,21 @@ export default class PostStreamScrubber extends Component { this.state = this.props.state; this.handlers = {}; - this.scrollListener = new ScrollListener(this.updateScrubberValues.bind(this)); + this.scrollListener = new ScrollListener(this.updateScrubberValues.bind(this, { fromScroll: true, forceHeightChange: true })); } view() { - const index = this.state.index; const count = this.state.count(); - const visible = this.state.visible() || 1; - const unreadCount = this.state.discussion.unreadCount(); - const unreadPercent = count ? Math.min(count - this.state.index, unreadCount) / count : 0; + // Index is left blank for performance reasons, it is filled in in updateScubberValues const viewing = app.translator.transChoice('core.forum.post_scrubber.viewing_text', count, { - index: {formatNumber(this.state.sanitizeIndex(index + 1))}, + index: , count: {formatNumber(count)}, }); + const unreadCount = this.state.discussion.unreadCount(); + const unreadPercent = count ? Math.min(count - this.state.index, unreadCount) / count : 0; + function styleUnread(element, isInitialized, context) { const $element = $(element); const newStyle = { @@ -47,15 +47,7 @@ export default class PostStreamScrubber extends Component { context.oldStyle = newStyle; } - - const percentPerPost = this.percentPerPost(); - const beforeHeight = Math.max(0, percentPerPost.index * Math.min(index, count - visible)); - const handleHeight = Math.min(100 - beforeHeight, percentPerPost.visible * visible); - const afterHeight = 100 - beforeHeight - handleHeight; - const classNames = ['PostStreamScrubber', 'Dropdown']; - if (this.state.disabled()) classNames.push('disabled'); - if (this.dragging) classNames.push('dragging'); if (this.props.className) classNames.push(this.props.className); return ( @@ -71,15 +63,15 @@ export default class PostStreamScrubber extends Component {
-
-
+
+
{viewing} {this.state.description}
-
+
{app.translator.trans('core.forum.post_scrubber.unread_text', { count: unreadCount })} @@ -130,6 +122,7 @@ export default class PostStreamScrubber extends Component { */ goToFirst() { this.state.goToFirst(); + this.updateScrubberValues({ animate: true, forceHeightChange: true }); } /** @@ -137,10 +130,12 @@ export default class PostStreamScrubber extends Component { */ goToLast() { this.state.goToLast(); + this.updateScrubberValues({ animate: true, forceHeightChange: true }); } config(isInitialized, context) { if (isInitialized) return; + this.state.loadPromise.then(() => this.updateScrubberValues({ animate: true })); context.onunload = this.ondestroy.bind(this); @@ -248,6 +243,7 @@ export default class PostStreamScrubber extends Component { // content that we want to load those posts. const intIndex = Math.floor(this.state.index); this.state.goToIndex(intIndex); + this.updateScrubberValues({ animate: true, forceHeightChange: true }); } onclick(e) { @@ -269,28 +265,53 @@ export default class PostStreamScrubber extends Component { let offsetIndex = offsetPercent / this.percentPerPost().index; offsetIndex = Math.max(0, Math.min(this.state.count() - 1, offsetIndex)); this.state.goToIndex(Math.floor(offsetIndex)); + this.updateScrubberValues({ animate: true, forceHeightChange: true }); this.$().removeClass('open'); } - updateScrubberValues() { - console.log(this.dragging); + /** + * Update the scrollbar's position to reflect the current values of the + * index/visible properties. + * + * @param {Boolean} animate + */ + updateScrubberValues(options = {}) { const index = this.state.index; const count = this.state.count(); const visible = this.state.visible() || 1; const percentPerPost = this.percentPerPost(); - this.$(`.Scrubber-index`).html(formatNumber(this.state.sanitizeIndex(index + 1))); + const $scrubber = this.$(); + $scrubber.find(`.Scrubber-index`).html(formatNumber(this.state.sanitizeIndex(index + 1))); const heights = {}; heights.before = Math.max(0, percentPerPost.index * Math.min(index, count - visible)); heights.handle = Math.min(100 - heights.before, percentPerPost.visible * visible); heights.after = 100 - heights.before - heights.handle; - for (const part in heights) { - this.$(`.Scrubber-${part}`).css('height', heights[part] + '%'); + console.log(heights.after); + + if (!(options.fromScroll && this.state.paused) && (!this.adjustingHeight || options.forceHeightChange)) { + const func = options.animate ? 'animate' : 'css'; + this.adjustingHeight = true; + const animationPromises = []; + for (const part in heights) { + const $part = $scrubber.find(`.Scrubber-${part}`); + animationPromises.push( + $part + .stop(true, true) + [func]({ height: heights[part] + '%' }, 'fast') + .promise() + ); + + // jQuery likes to put overflow:hidden, but because the scrollbar handle + // has a negative margin-left, we need to override. + if (func === 'animate') $part.css('overflow', 'visible'); + } + Promise.all(animationPromises).then(() => (this.adjustingHeight = false)); } - this.$().toggleClass('disabled', this.state.disabled()); + $scrubber.toggleClass('disabled', this.state.disabled()); } } diff --git a/js/src/forum/states/PostStreamState.js b/js/src/forum/states/PostStreamState.js index 3c22eddae..09deb2da6 100644 --- a/js/src/forum/states/PostStreamState.js +++ b/js/src/forum/states/PostStreamState.js @@ -234,6 +234,7 @@ class PostStreamState { */ loadPage(start, end, backwards) { console.log('loadPage'); + m.redraw(); this.loadPageTimeouts[start] = setTimeout( () => { diff --git a/less/forum/Scrubber.less b/less/forum/Scrubber.less index d23e1857a..4d853e89d 100644 --- a/less/forum/Scrubber.less +++ b/less/forum/Scrubber.less @@ -7,7 +7,8 @@ font-size: 14px; margin-right: 2px; } - &:hover, &:focus { + &:hover, + &:focus { text-decoration: none; color: @link-color; } @@ -19,22 +20,20 @@ min-height: 50px; // JavaScript sets a max-height position: relative; } -.Scrubber-before, .Scrubber-after { +.Scrubber-before, +.Scrubber-after { border-left: 1px solid @control-bg; - transition: height 0.15s linear; -} -.Scrubber-handle { - border-left: 1px solid @control-bg; - transition: height 0.25s ease-in; -} -.dragging .Scrubber-before, .dragging .Scrubber-after, .dragging .Scrubber-handle { - transition: none; } .Scrubber-unread { position: absolute; border-left: 1px solid lighten(@muted-color, 10%); width: 100%; - background-image: linear-gradient(to right, @control-bg, fade(@control-bg, 0) 10px, fade(@control-bg, 0)); + background-image: linear-gradient( + to right, + @control-bg, + fade(@control-bg, 0) 10px, + fade(@control-bg, 0) + ); display: flex; align-items: center; color: @muted-color;