mirror of
https://github.com/flarum/framework.git
synced 2024-11-24 12:04:39 +08:00
Add infinite scrolling in the notifications list
This commit is contained in:
parent
3dcfe32b27
commit
77c25ab725
206
js/forum/dist/app.js
vendored
206
js/forum/dist/app.js
vendored
|
@ -23964,39 +23964,18 @@ System.register('flarum/components/NotificationList', ['flarum/Component', 'flar
|
|||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
|
||||
/**
|
||||
* Whether or not there are more results that can be loaded.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.moreResults = false;
|
||||
}
|
||||
}, {
|
||||
key: 'view',
|
||||
value: function view() {
|
||||
var groups = [];
|
||||
|
||||
if (app.cache.notifications) {
|
||||
var discussions = {};
|
||||
|
||||
// Build an array of discussions which the notifications are related to,
|
||||
// and add the notifications as children.
|
||||
app.cache.notifications.forEach(function (notification) {
|
||||
var subject = notification.subject();
|
||||
|
||||
if (typeof subject === 'undefined') return;
|
||||
|
||||
// Get the discussion that this notification is related to. If it's not
|
||||
// directly related to a discussion, it may be related to a post or
|
||||
// other entity which is related to a discussion.
|
||||
var discussion = false;
|
||||
if (subject instanceof Discussion) discussion = subject;else if (subject && subject.discussion) discussion = subject.discussion();
|
||||
|
||||
// If the notification is not related to a discussion directly or
|
||||
// indirectly, then we will assign it to a neutral group.
|
||||
var key = discussion ? discussion.id() : 0;
|
||||
discussions[key] = discussions[key] || { discussion: discussion, notifications: [] };
|
||||
discussions[key].notifications.push(notification);
|
||||
|
||||
if (groups.indexOf(discussions[key]) === -1) {
|
||||
groups.push(discussions[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
var pages = app.cache.notifications || [];
|
||||
|
||||
return m(
|
||||
'div',
|
||||
|
@ -24023,71 +24002,144 @@ System.register('flarum/components/NotificationList', ['flarum/Component', 'flar
|
|||
m(
|
||||
'div',
|
||||
{ className: 'NotificationList-content' },
|
||||
groups.length ? groups.map(function (group) {
|
||||
var badges = group.discussion && group.discussion.badges().toArray();
|
||||
pages.length ? pages.map(function (notifications) {
|
||||
var groups = [];
|
||||
var discussions = {};
|
||||
|
||||
return m(
|
||||
'div',
|
||||
{ className: 'NotificationGroup' },
|
||||
group.discussion ? m(
|
||||
'a',
|
||||
{ className: 'NotificationGroup-header',
|
||||
href: app.route.discussion(group.discussion),
|
||||
config: m.route },
|
||||
badges && badges.length ? m(
|
||||
'ul',
|
||||
{ className: 'NotificationGroup-badges badges' },
|
||||
listItems(badges)
|
||||
) : '',
|
||||
group.discussion.title()
|
||||
) : m(
|
||||
notifications.forEach(function (notification) {
|
||||
var subject = notification.subject();
|
||||
|
||||
if (typeof subject === 'undefined') return;
|
||||
|
||||
// Get the discussion that this notification is related to. If it's not
|
||||
// directly related to a discussion, it may be related to a post or
|
||||
// other entity which is related to a discussion.
|
||||
var discussion = false;
|
||||
if (subject instanceof Discussion) discussion = subject;else if (subject && subject.discussion) discussion = subject.discussion();
|
||||
|
||||
// If the notification is not related to a discussion directly or
|
||||
// indirectly, then we will assign it to a neutral group.
|
||||
var key = discussion ? discussion.id() : 0;
|
||||
discussions[key] = discussions[key] || { discussion: discussion, notifications: [] };
|
||||
discussions[key].notifications.push(notification);
|
||||
|
||||
if (groups.indexOf(discussions[key]) === -1) {
|
||||
groups.push(discussions[key]);
|
||||
}
|
||||
});
|
||||
|
||||
return groups.map(function (group) {
|
||||
var badges = group.discussion && group.discussion.badges().toArray();
|
||||
|
||||
return m(
|
||||
'div',
|
||||
{ className: 'NotificationGroup-header' },
|
||||
app.forum.attribute('title')
|
||||
),
|
||||
m(
|
||||
'ul',
|
||||
{ className: 'NotificationGroup-content' },
|
||||
group.notifications.map(function (notification) {
|
||||
var NotificationComponent = app.notificationComponents[notification.contentType()];
|
||||
return NotificationComponent ? m(
|
||||
'li',
|
||||
null,
|
||||
NotificationComponent.component({ notification: notification })
|
||||
) : '';
|
||||
})
|
||||
)
|
||||
);
|
||||
}) : !this.loading ? m(
|
||||
{ className: 'NotificationGroup' },
|
||||
group.discussion ? m(
|
||||
'a',
|
||||
{ className: 'NotificationGroup-header',
|
||||
href: app.route.discussion(group.discussion),
|
||||
config: m.route },
|
||||
badges && badges.length ? m(
|
||||
'ul',
|
||||
{ className: 'NotificationGroup-badges badges' },
|
||||
listItems(badges)
|
||||
) : '',
|
||||
group.discussion.title()
|
||||
) : m(
|
||||
'div',
|
||||
{ className: 'NotificationGroup-header' },
|
||||
app.forum.attribute('title')
|
||||
),
|
||||
m(
|
||||
'ul',
|
||||
{ className: 'NotificationGroup-content' },
|
||||
group.notifications.map(function (notification) {
|
||||
var NotificationComponent = app.notificationComponents[notification.contentType()];
|
||||
return NotificationComponent ? m(
|
||||
'li',
|
||||
null,
|
||||
NotificationComponent.component({ notification: notification })
|
||||
) : '';
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
}) : '',
|
||||
this.loading ? m(LoadingIndicator, { className: 'LoadingIndicator--block' }) : pages.length ? '' : m(
|
||||
'div',
|
||||
{ className: 'NotificationList-empty' },
|
||||
app.translator.trans('core.forum.notifications.empty_text')
|
||||
) : LoadingIndicator.component({ className: 'LoadingIndicator--block' })
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}, {
|
||||
key: 'load',
|
||||
value: function load() {
|
||||
key: 'config',
|
||||
value: function config(isInitialized, context) {
|
||||
var _this2 = this;
|
||||
|
||||
if (app.cache.notifications && !app.session.user.newNotificationsCount()) {
|
||||
if (isInitialized) return;
|
||||
|
||||
var $notifications = this.$('.NotificationList-content');
|
||||
var $scrollParent = $notifications.css('overflow') === 'auto' ? $notifications : $(window);
|
||||
|
||||
var scrollHandler = function scrollHandler() {
|
||||
var scrollTop = $scrollParent.scrollTop();
|
||||
var viewportHeight = $scrollParent.height();
|
||||
var contentTop = $scrollParent === $notifications ? 0 : $notifications.offset().top;
|
||||
var contentHeight = $notifications[0].scrollHeight;
|
||||
|
||||
if (_this2.moreResults && !_this2.loading && scrollTop + viewportHeight >= contentTop + contentHeight) {
|
||||
_this2.loadMore();
|
||||
}
|
||||
};
|
||||
|
||||
$scrollParent.on('scroll', scrollHandler);
|
||||
|
||||
context.onunload = function () {
|
||||
$scrollParent.off('scroll', scrollHandler);
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: 'load',
|
||||
value: function load() {
|
||||
if (app.session.user.newNotificationsCount()) {
|
||||
delete app.cache.notifications;
|
||||
}
|
||||
|
||||
if (app.cache.notifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.session.user.pushAttributes({ newNotificationsCount: 0 });
|
||||
|
||||
this.loadMore();
|
||||
}
|
||||
}, {
|
||||
key: 'loadMore',
|
||||
value: function loadMore() {
|
||||
var _this3 = this;
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
app.store.find('notifications').then(function (notifications) {
|
||||
app.session.user.pushAttributes({ newNotificationsCount: 0 });
|
||||
app.cache.notifications = notifications.sort(function (a, b) {
|
||||
return b.time() - a.time();
|
||||
});
|
||||
}).catch(function () {}).then(function () {
|
||||
_this2.loading = false;
|
||||
var params = app.cache.notifications ? { page: { offset: app.cache.notifications.length * 10 } } : null;
|
||||
|
||||
return app.store.find('notifications', params).then(this.parseResults.bind(this)).catch(function () {}).then(function () {
|
||||
_this3.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'parseResults',
|
||||
value: function parseResults(results) {
|
||||
app.cache.notifications = app.cache.notifications || [];
|
||||
app.cache.notifications.push(results);
|
||||
|
||||
this.moreResults = !!results.payload.links.next;
|
||||
|
||||
return results;
|
||||
}
|
||||
}, {
|
||||
key: 'markAllAsRead',
|
||||
value: function markAllAsRead() {
|
||||
|
@ -24095,8 +24147,10 @@ System.register('flarum/components/NotificationList', ['flarum/Component', 'flar
|
|||
|
||||
app.session.user.pushAttributes({ unreadNotificationsCount: 0 });
|
||||
|
||||
app.cache.notifications.forEach(function (notification) {
|
||||
return notification.pushAttributes({ isRead: true });
|
||||
app.cache.notifications.forEach(function (notifications) {
|
||||
notifications.forEach(function (notification) {
|
||||
return notification.pushAttributes({ isRead: true });
|
||||
});
|
||||
});
|
||||
|
||||
app.request({
|
||||
|
|
|
@ -16,39 +16,17 @@ export default class NotificationList extends Component {
|
|||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
|
||||
/**
|
||||
* Whether or not there are more results that can be loaded.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.moreResults = false;
|
||||
}
|
||||
|
||||
view() {
|
||||
const groups = [];
|
||||
|
||||
if (app.cache.notifications) {
|
||||
const discussions = {};
|
||||
|
||||
// Build an array of discussions which the notifications are related to,
|
||||
// and add the notifications as children.
|
||||
app.cache.notifications.forEach(notification => {
|
||||
const subject = notification.subject();
|
||||
|
||||
if (typeof subject === 'undefined') return;
|
||||
|
||||
// Get the discussion that this notification is related to. If it's not
|
||||
// directly related to a discussion, it may be related to a post or
|
||||
// other entity which is related to a discussion.
|
||||
let discussion = false;
|
||||
if (subject instanceof Discussion) discussion = subject;
|
||||
else if (subject && subject.discussion) discussion = subject.discussion();
|
||||
|
||||
// If the notification is not related to a discussion directly or
|
||||
// indirectly, then we will assign it to a neutral group.
|
||||
const key = discussion ? discussion.id() : 0;
|
||||
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
||||
discussions[key].notifications.push(notification);
|
||||
|
||||
if (groups.indexOf(discussions[key]) === -1) {
|
||||
groups.push(discussions[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
const pages = app.cache.notifications || [];
|
||||
|
||||
return (
|
||||
<div className="NotificationList">
|
||||
|
@ -66,8 +44,34 @@ export default class NotificationList extends Component {
|
|||
</div>
|
||||
|
||||
<div className="NotificationList-content">
|
||||
{groups.length
|
||||
? groups.map(group => {
|
||||
{pages.length ? pages.map(notifications => {
|
||||
const groups = [];
|
||||
const discussions = {};
|
||||
|
||||
notifications.forEach(notification => {
|
||||
const subject = notification.subject();
|
||||
|
||||
if (typeof subject === 'undefined') return;
|
||||
|
||||
// Get the discussion that this notification is related to. If it's not
|
||||
// directly related to a discussion, it may be related to a post or
|
||||
// other entity which is related to a discussion.
|
||||
let discussion = false;
|
||||
if (subject instanceof Discussion) discussion = subject;
|
||||
else if (subject && subject.discussion) discussion = subject.discussion();
|
||||
|
||||
// If the notification is not related to a discussion directly or
|
||||
// indirectly, then we will assign it to a neutral group.
|
||||
const key = discussion ? discussion.id() : 0;
|
||||
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
||||
discussions[key].notifications.push(notification);
|
||||
|
||||
if (groups.indexOf(discussions[key]) === -1) {
|
||||
groups.push(discussions[key]);
|
||||
}
|
||||
});
|
||||
|
||||
return groups.map(group => {
|
||||
const badges = group.discussion && group.discussion.badges().toArray();
|
||||
|
||||
return (
|
||||
|
@ -94,32 +98,71 @@ export default class NotificationList extends Component {
|
|||
</ul>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: !this.loading
|
||||
? <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>
|
||||
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
|
||||
});
|
||||
}) : ''}
|
||||
{this.loading
|
||||
? <LoadingIndicator className="LoadingIndicator--block" />
|
||||
: (pages.length ? '' : <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
config(isInitialized, context) {
|
||||
if (isInitialized) return;
|
||||
|
||||
const $notifications = this.$('.NotificationList-content');
|
||||
const $scrollParent = $notifications.css('overflow') === 'auto' ? $notifications : $(window);
|
||||
|
||||
const scrollHandler = () => {
|
||||
const scrollTop = $scrollParent.scrollTop();
|
||||
const viewportHeight = $scrollParent.height();
|
||||
const contentTop = $scrollParent === $notifications ? 0 : $notifications.offset().top;
|
||||
const contentHeight = $notifications[0].scrollHeight;
|
||||
|
||||
if (this.moreResults && !this.loading && scrollTop + viewportHeight >= contentTop + contentHeight) {
|
||||
this.loadMore();
|
||||
}
|
||||
};
|
||||
|
||||
$scrollParent.on('scroll', scrollHandler);
|
||||
|
||||
context.onunload = () => {
|
||||
$scrollParent.off('scroll', scrollHandler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load notifications into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (app.cache.notifications && !app.session.user.newNotificationsCount()) {
|
||||
if (app.session.user.newNotificationsCount()) {
|
||||
delete app.cache.notifications;
|
||||
}
|
||||
|
||||
if (app.cache.notifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.session.user.pushAttributes({newNotificationsCount: 0});
|
||||
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the next page of notification results.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
loadMore() {
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
app.store.find('notifications')
|
||||
.then(notifications => {
|
||||
app.session.user.pushAttributes({newNotificationsCount: 0});
|
||||
app.cache.notifications = notifications.sort((a, b) => b.time() - a.time());
|
||||
})
|
||||
const params = app.cache.notifications ? {page: {offset: app.cache.notifications.length * 10}} : null;
|
||||
|
||||
return app.store.find('notifications', params)
|
||||
.then(this.parseResults.bind(this))
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
|
@ -127,6 +170,21 @@ export default class NotificationList extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse results and append them to the notification list.
|
||||
*
|
||||
* @param {Notification[]} results
|
||||
* @return {Notification[]}
|
||||
*/
|
||||
parseResults(results) {
|
||||
app.cache.notifications = app.cache.notifications || [];
|
||||
app.cache.notifications.push(results);
|
||||
|
||||
this.moreResults = !!results.payload.links.next;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all of the notifications as read.
|
||||
*/
|
||||
|
@ -135,7 +193,9 @@ export default class NotificationList extends Component {
|
|||
|
||||
app.session.user.pushAttributes({unreadNotificationsCount: 0});
|
||||
|
||||
app.cache.notifications.forEach(notification => notification.pushAttributes({isRead: true}));
|
||||
app.cache.notifications.forEach(notifications => {
|
||||
notifications.forEach(notification => notification.pushAttributes({isRead: true}))
|
||||
});
|
||||
|
||||
app.request({
|
||||
url: app.forum.attribute('apiUrl') + '/notifications/read',
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
.NotificationList-content {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
& .Dropdown-toggle .Button-label {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\UrlGenerator;
|
||||
use Flarum\Core\Discussion;
|
||||
use Flarum\Core\Exception\PermissionDeniedException;
|
||||
use Flarum\Core\Repository\NotificationRepository;
|
||||
|
@ -39,16 +40,23 @@ class ListNotificationsController extends AbstractCollectionController
|
|||
public $limit = 10;
|
||||
|
||||
/**
|
||||
* @var \Flarum\Core\Repository\NotificationRepository
|
||||
* @var NotificationRepository
|
||||
*/
|
||||
protected $notifications;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Repository\NotificationRepository $notifications
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
public function __construct(NotificationRepository $notifications)
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @param NotificationRepository $notifications
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(NotificationRepository $notifications, UrlGenerator $url)
|
||||
{
|
||||
$this->notifications = $notifications;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,10 +76,25 @@ class ListNotificationsController extends AbstractCollectionController
|
|||
$offset = $this->extractOffset($request);
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
$notifications = $this->notifications->findByUser($actor, $limit, $offset)
|
||||
$notifications = $this->notifications->findByUser($actor, $limit + 1, $offset)
|
||||
->load(array_diff($include, ['subject.discussion']))
|
||||
->all();
|
||||
|
||||
$areMoreResults = false;
|
||||
|
||||
if (count($notifications) > $limit) {
|
||||
array_pop($notifications);
|
||||
$areMoreResults = true;
|
||||
}
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->toRoute('notifications.index'),
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
$areMoreResults ? null : 0
|
||||
);
|
||||
|
||||
if (in_array('subject.discussion', $include)) {
|
||||
$this->loadSubjectDiscussions($notifications);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user