1
0
mirror of https://github.com/flarum/core.git synced 2025-05-10 17:35:25 +02:00
php-flarum/js/forum/src/components/discussion-page.js
Toby Zerner e8bb044701 Discussion list refactor, gestures
Also make base Component class automatically assign this.element :)
2015-06-24 17:56:39 +09:30

249 lines
7.4 KiB
JavaScript

import Component from 'flarum/component';
import ItemList from 'flarum/utils/item-list';
import DiscussionList from 'flarum/components/discussion-list';
import DiscussionHero from 'flarum/components/discussion-hero';
import PostStream from 'flarum/components/post-stream';
import PostScrubber from 'flarum/components/post-scrubber';
import ReplyComposer from 'flarum/components/reply-composer';
import ActionButton from 'flarum/components/action-button';
import LoadingIndicator from 'flarum/components/loading-indicator';
import DropdownSplit from 'flarum/components/dropdown-split';
import Separator from 'flarum/components/separator';
import listItems from 'flarum/helpers/list-items';
import mixin from 'flarum/utils/mixin';
import evented from 'flarum/utils/evented';
export default class DiscussionPage extends mixin(Component, evented) {
/**
*/
constructor(props) {
super(props);
this.discussion = m.prop();
this.refresh();
if (app.cache.discussionList) {
if (app.current instanceof DiscussionPage) {
m.redraw.strategy('diff'); // otherwise pane redraws (killing retained subtrees) and mouseenter event is triggered so it doesn't hide
}
app.pane.enable();
app.pane.hide();
}
app.history.push('discussion');
app.current = this;
app.session.on('loggedIn', this.loggedInHandler = this.refresh.bind(this));
}
refresh() {
this.currentNear = m.route.param('near') || 0;
this.discussion(null);
var params = this.params();
params.include = params.include.join(',');
var discussion;
if (app.preload.response) {
// We must wrap this in a setTimeout because if we are mounting this
// component for the first time on page load, then any calls to m.redraw
// will be ineffective and thus any configs (scroll code) will be run
// before stuff is drawn to the page.
setTimeout(() => {
var discussion = app.store.pushPayload(app.preload.response);
app.preload.response = null;
this.setupDiscussion(discussion);
});
} else {
app.store.find('discussions', m.route.param('id'), params).then(this.setupDiscussion.bind(this));
}
// Trigger a redraw only if we're not already in a computation (e.g. route change)
m.startComputation();
m.endComputation();
}
params() {
return {
near: this.currentNear,
include: ['posts', 'posts.user', 'posts.user.groups']
};
}
/*
*/
setupDiscussion(discussion) {
// Hold up there skippy! If the slug in the URL doesn't match up, we'll
// redirect so we have the correct one.
// Waiting on https://github.com/lhorie/mithril.js/issues/539
// if (m.route.param('id') === discussion.id() && m.route.param('slug') !== discussion.slug()) {
// var params = m.route.param();
// params.slug = discussion.slug();
// params.near = params.near || '';
// m.route(app.route('discussion.near', params), null, true);
// return;
// }
this.discussion(discussion);
app.setTitle(discussion.title());
var includedPosts = [];
if (discussion.payload && discussion.payload.included) {
discussion.payload.included.forEach(record => {
if (record.type === 'posts' && record.links && record.links.discussion) {
includedPosts.push(app.store.getById('posts', record.id));
}
});
includedPosts.sort((a, b) => a.id() - b.id()).splice(20);
}
this.stream = new PostStream({ discussion, includedPosts });
this.stream.on('positionChanged', this.positionChanged.bind(this));
this.stream.goToNumber(m.route.param('near') || 1, true);
this.trigger('loaded', discussion);
}
onload(element, isInitialized, context) {
if (isInitialized) { return; }
context.retain = true;
$('body').addClass('discussion-page');
context.onunload = function() {
$('body').removeClass('discussion-page');
}
}
/**
*/
onunload(e) {
// If we have routed to the same discussion as we were viewing previously,
// cancel the unloading of this controller and instead prompt the post
// stream to jump to the new 'near' param.
var discussion = this.discussion();
if (discussion) {
if (m.route.param('id') == discussion.id()) {
e.preventDefault();
if (m.route.param('near') != this.currentNear) {
this.stream.goToNumber(m.route.param('near') || 1);
}
this.currentNear = null;
return;
}
}
app.pane.disable();
app.composer.minimize();
app.session.off('loggedIn', this.loggedInHandler);
}
/**
*/
view() {
var discussion = this.discussion();
return m('div', {config: this.onload.bind(this)}, [
app.cache.discussionList ? m('div.index-area.paned', {config: this.configIndex.bind(this)}, app.cache.discussionList.view()) : '',
m('div.discussion-area', discussion ? [
DiscussionHero.component({discussion}),
m('div.container', [
m('nav.discussion-nav', [
m('ul', listItems(this.sidebarItems().toArray()))
]),
this.stream.view()
])
] : LoadingIndicator.component({className: 'loading-indicator-block'}))
]);
}
/**
*/
configIndex(element, isInitialized, context) {
if (isInitialized) { return; }
context.retain = true;
var $index = $(element);
// When viewing a discussion (for which the discussions route is the
// parent,) the discussion list is still rendered but it becomes a
// pane hidden on the side of the screen. When the mouse enters and
// leaves the discussions pane, we want to show and hide the pane
// respectively. We also create a 10px 'hot edge' on the left of the
// screen to activate the pane.
var pane = app.pane;
$index.hover(pane.show.bind(pane), pane.onmouseleave.bind(pane));
var hotEdge = function(e) {
if (e.pageX < 10) { pane.show(); }
};
$(document).on('mousemove', hotEdge);
context.onunload = function() {
$(document).off('mousemove', hotEdge);
};
var $discussion = $index.find('.discussion-summary.active');
if ($discussion.length) {
var indexTop = $index.offset().top;
var discussionTop = $discussion.offset().top;
if (discussionTop < indexTop || discussionTop + $discussion.outerHeight() > indexTop + $index.outerHeight()) {
$index.scrollTop($index.scrollTop() - indexTop + discussionTop);
}
}
}
/**
*/
sidebarItems() {
var items = new ItemList();
items.add('controls',
DropdownSplit.component({
items: this.discussion().controls(this).toArray(),
icon: 'ellipsis-v',
className: 'primary-control',
buttonClass: 'btn btn-primary'
})
);
items.add('scrubber',
PostScrubber.component({
stream: this.stream,
className: 'title-control'
})
);
return items;
}
/**
*/
positionChanged(startNumber, endNumber) {
var discussion = this.discussion();
var url = app.route('discussion.near', {
id: discussion.id(),
slug: discussion.slug(),
near: this.currentNear = startNumber
});
// https://github.com/lhorie/mithril.js/issues/559
m.route(url, true);
window.history.replaceState(null, document.title, (m.route.mode === 'hash' ? '#' : '')+url);
app.history.push('discussion');
if (app.session.user() && endNumber > discussion.readNumber()) {
discussion.save({readNumber: endNumber});
m.redraw();
}
}
}