mirror of
https://github.com/flarum/framework.git
synced 2024-12-12 14:13:37 +08:00
281 lines
9.2 KiB
JavaScript
281 lines
9.2 KiB
JavaScript
import Ember from 'ember';
|
|
|
|
import MenuItem from '../components/menu-item';
|
|
import Scrollbar from '../utils/scrollbar';
|
|
import PostStreamMixin from '../mixins/post-stream';
|
|
|
|
export default Ember.View.extend(PostStreamMixin, {
|
|
|
|
/**
|
|
@property templateName
|
|
@type String
|
|
*/
|
|
templateName: 'discussion-scrollbar',
|
|
classNames: ['scrubber', 'discussion-scrubber'],
|
|
|
|
// An object which represents/ecapsulates the scrollbar.
|
|
scrollbar: null,
|
|
|
|
// Right after the controller finished loading a discussion, we want to
|
|
// trigger a scroll event on the window so the interface is kept up-to-date.
|
|
loadedChanged: function() {
|
|
this.scrollbar.setDisabled(! this.get('controller.loaded'));
|
|
}.observes('controller.loaded'),
|
|
|
|
countChanged: function() {
|
|
this.scrollbar.setCount(this.get('controller.postStream.count'));
|
|
}.observes('controller.postStream.count'),
|
|
|
|
windowWasResized: function(event) {
|
|
var view = event.data.view;
|
|
view.scrollbar.$.height($('#sidebar-content').height() + $('#sidebar-content').offset().top - view.scrollbar.$.offset().top - 80);
|
|
view.scrollbar.update();
|
|
},
|
|
|
|
windowWasScrolled: function(event) {
|
|
var view = event.data.view,
|
|
$window = $(window);
|
|
|
|
if (! view.get('controller.loaded') || $window.data('disableScrollHandler')) {
|
|
return;
|
|
}
|
|
|
|
var scrollTop = $window.scrollTop(),
|
|
windowHeight = $window.height();
|
|
|
|
// Before looping through all of the posts, we reset the scrollbar
|
|
// properties to a 'default' state. These values reflect what would be
|
|
// seen if the browser were scrolled right up to the top of the page,
|
|
// and the viewport had a height of 0.
|
|
var index = $('.posts .item:first').data('end');
|
|
var visiblePosts = 0;
|
|
var period = '';
|
|
|
|
var first = $('.posts .item[data-start=0]');
|
|
var offsetTop = first.length ? first.offset().top : 0;
|
|
|
|
// Now loop through each of the items in the discussion. An 'item' is
|
|
// either a single post or a 'gap' of one or more posts that haven't
|
|
// been loaded yet.
|
|
// @todo cache item top positions to speed this up?
|
|
$('.posts .item').each(function(k) {
|
|
var $this = $(this),
|
|
top = $this.offset().top - offsetTop,
|
|
height = $this.outerHeight();
|
|
|
|
// If this item is above the top of the viewport, skip to the next
|
|
// post. If it's below the bottom of the viewport, break out of the
|
|
// loop.
|
|
if (top + height < scrollTop) {
|
|
return;
|
|
}
|
|
if (top > scrollTop + windowHeight) {
|
|
return false;
|
|
}
|
|
|
|
// If the bottom half of this item is visible at the top of the
|
|
// viewport, then add the visible proportion to the visiblePosts
|
|
// counter, and set the scrollbar index to whatever the visible
|
|
// proportion represents. For example, if a gap represents indexes
|
|
// 0-9, and the bottom 50% of the gap is visible in the viewport,
|
|
// then the scrollbar index will be 5.
|
|
if (top <= scrollTop && top + height > scrollTop) {
|
|
visiblePosts = (top + height - scrollTop) / height;
|
|
index = parseFloat($this.data('end')) + 1 - visiblePosts;
|
|
}
|
|
|
|
// If the top half of this item is visible at the bottom of the
|
|
// viewport, then add the visible proportion to the visiblePosts
|
|
// counter.
|
|
else if (top + height >= scrollTop + windowHeight) {
|
|
visiblePosts += (scrollTop + windowHeight - top) / height;
|
|
}
|
|
|
|
// If the whole item is visible in the viewport, then increment the
|
|
// visiblePosts counter.
|
|
else {
|
|
visiblePosts++;
|
|
}
|
|
|
|
// If this item has a time associated with it, then set the
|
|
// scrollbar's current period to a formatted version of this time.
|
|
if ($this.data('time')) {
|
|
period = $this.data('time');
|
|
}
|
|
});
|
|
|
|
// Now that we've looped through all of the items and have worked out
|
|
// the scrollbar's current index and the number of posts visible in the
|
|
// viewport, we can update the scrollbar.
|
|
view.scrollbar.setIndex(index);
|
|
view.scrollbar.setVisible(visiblePosts);
|
|
view.scrollbar.update();
|
|
|
|
view.scrollbar.$.find('.index').text(Math.ceil(index + visiblePosts));
|
|
view.scrollbar.$.find('.description').text(moment(period).format('MMMM YYYY'));
|
|
},
|
|
|
|
mouseWasMoved: function(event) {
|
|
var view = event.data.view;
|
|
|
|
if ( ! event.data.handle) {
|
|
return;
|
|
}
|
|
|
|
var offsetPixels = event.clientY - event.data.mouseStart;
|
|
var offsetPercent = offsetPixels / view.scrollbar.$.outerHeight() * 100;
|
|
|
|
var offsetIndex = offsetPercent / view.scrollbar.percentPerPost().index;
|
|
var newIndex = Math.max(0, Math.min(event.data.indexStart + offsetIndex, view.scrollbar.count - 1));
|
|
|
|
view.scrollToIndex(newIndex);
|
|
},
|
|
|
|
mouseWasReleased: function(event) {
|
|
var view = event.data.view;
|
|
|
|
if (! event.data.handle) {
|
|
return;
|
|
}
|
|
|
|
event.data.mouseStart = 0;
|
|
event.data.indexStart = 0;
|
|
event.data.handle = null;
|
|
|
|
$(window).data('disableScrollHandler', false);
|
|
|
|
view.get('controller').send('jumpToIndex', Math.floor(view.scrollbar.index));
|
|
|
|
$(window).scroll();
|
|
$('body').css('cursor', '');
|
|
},
|
|
|
|
didInsertElement: function() {
|
|
var view = this;
|
|
|
|
// Set up scrollbar object
|
|
this.scrollbar = new Scrollbar($('.discussion-scrubber .scrollbar'));
|
|
this.scrollbar.setDisabled(true);
|
|
this.countChanged();
|
|
this.loadedChanged();
|
|
|
|
// Whenever the window is resized, adjust the height of the scrollbar
|
|
// so that it fills the height of the sidebar.
|
|
$(window).on('resize', {view: this}, this.windowWasResized).resize();
|
|
|
|
// Define a handler to update the state of the scrollbar to reflect the
|
|
// current scroll position of the page.
|
|
$(window).on('scroll', {view: this}, this.windowWasScrolled);
|
|
|
|
this.get('controller').on('loadingIndex', this, this.loadingIndex);
|
|
|
|
// Now we want to make the scrollbar handle draggable. Let's start by
|
|
// preventing default browser events from messing things up.
|
|
this.scrollbar.$
|
|
.css('user-select', 'none')
|
|
.bind('dragstart mousedown', function(e) {
|
|
e.preventDefault();
|
|
});
|
|
|
|
// When the mouse is pressed on the scrollbar handle, we need to capture
|
|
// some information about the current position.
|
|
var scrollData = {
|
|
view: this,
|
|
mouseStart: 0,
|
|
indexStart: 0,
|
|
handle: null
|
|
};
|
|
|
|
this.scrollbar.$.find('.scrollbar-slider').css('cursor', 'move').mousedown(function(e) {
|
|
scrollData.mouseStart = e.clientY;
|
|
scrollData.indexStart = view.scrollbar.index;
|
|
scrollData.handle = $(this);
|
|
$(window).data('disableScrollHandler', true);
|
|
$('body').css('cursor', 'move');
|
|
});
|
|
|
|
// When the mouse moves,
|
|
$(document)
|
|
.on('mousemove', scrollData, this.mouseWasMoved)
|
|
.on('mouseup', scrollData, this.mouseWasReleased);
|
|
|
|
// When any part of the whole scrollbar is clicked, we want to jump to
|
|
// that position.
|
|
this.scrollbar.$.click(function(e) {
|
|
|
|
// Calculate the index which we want to jump to.
|
|
// @todo document how this complexity works.
|
|
var offsetPixels = e.clientY - view.scrollbar.$.offset().top + $('body').scrollTop();
|
|
var offsetPercent = offsetPixels / view.scrollbar.$.outerHeight() * 100;
|
|
|
|
var handleHeight = parseFloat(view.scrollbar.$.find('.scrollbar-slider')[0].style.height);
|
|
|
|
var offsetIndex = (offsetPercent - handleHeight / 2) / view.scrollbar.percentPerPost().index;
|
|
var newIndex = Math.max(0, Math.min(view.scrollbar.count - 1, offsetIndex));
|
|
|
|
view.get('controller').send('jumpToIndex', Math.floor(newIndex));
|
|
})
|
|
|
|
// Exempt the scrollbar handle from this 'jump to' click event.
|
|
this.scrollbar.$.find('.scrollbar-slider').click(function(e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
},
|
|
|
|
actions: {
|
|
firstPost: function() {
|
|
this.get('controller').send('jumpToIndex', 0);
|
|
},
|
|
|
|
lastPost: function() {
|
|
this.get('controller').send('jumpToIndex', this.scrollbar.count - 1);
|
|
}
|
|
},
|
|
|
|
loadingIndex: function(index) {
|
|
this.scrollToIndex(index, true);
|
|
},
|
|
|
|
// Instantly scroll to a certain index in the discussion. The index doesn't
|
|
// have to be an integer; any fraction of a post will be scrolled to.
|
|
scrollToIndex: function(index, animate) {
|
|
index = Math.max(0, Math.min(index, this.scrollbar.count - 1));
|
|
var indexFloor = Math.floor(index);
|
|
|
|
// Find
|
|
var nearestItem = this.findNearestToIndex(indexFloor);
|
|
var first = $('.posts .item[data-start=0]');
|
|
var offsetTop = first.length ? first.offset().top : 0;
|
|
|
|
var pos = nearestItem.offset().top - offsetTop;
|
|
if (! nearestItem.is('.gap')) {
|
|
pos += nearestItem.outerHeight() * (index - indexFloor);
|
|
} else {
|
|
nearestItem.addClass('active');
|
|
}
|
|
|
|
$('.posts .item.gap').not(nearestItem).removeClass('active');
|
|
|
|
if (animate) {
|
|
// $('html, body').animate({scrollTop: pos});
|
|
} else {
|
|
$('html, body').scrollTop(pos);
|
|
}
|
|
this.scrollbar.setIndex(index);
|
|
this.scrollbar.update(animate);
|
|
},
|
|
|
|
willDestroyElement: function() {
|
|
$(window)
|
|
.off('resize', this.windowWasResized)
|
|
.off('scroll', this.windowWasScrolled);
|
|
|
|
$(document)
|
|
.off('mousemove', this.mouseWasMoved)
|
|
.off('mouseup', this.mouseWasReleased);
|
|
|
|
this.get('controller').off('loadingIndex', this, this.loadingIndex);
|
|
}
|
|
});
|