diff --git a/client/html/endless_pager.hbs b/client/html/endless_pager.hbs
new file mode 100644
index 00000000..b7346834
--- /dev/null
+++ b/client/html/endless_pager.hbs
@@ -0,0 +1,4 @@
+
diff --git a/client/html/endless_pager_page.hbs b/client/html/endless_pager_page.hbs
new file mode 100644
index 00000000..4f253ff2
--- /dev/null
+++ b/client/html/endless_pager_page.hbs
@@ -0,0 +1,4 @@
+
+
Page {{this.page}} of {{this.totalPages}}
+
+
diff --git a/client/js/controllers/page_controller.js b/client/js/controllers/page_controller.js
index 783f70b3..da30ec03 100644
--- a/client/js/controllers/page_controller.js
+++ b/client/js/controllers/page_controller.js
@@ -1,16 +1,33 @@
'use strict';
-const api = require('../api.js');
+const events = require('../events.js');
+const settingsController = require('./settings_controller.js');
+const EndlessPageView = require('../views/endless_page_view.js');
const ManualPageView = require('../views/manual_page_view.js');
class PageController {
constructor() {
- this.pageView = new ManualPageView();
+ events.listen(events.SettingsChange, () => {
+ this.update();
+ });
+ this.update();
+ }
+
+ update() {
+ if (settingsController.getSettings().endlessScroll) {
+ this.pageView = new EndlessPageView();
+ } else {
+ this.pageView = new ManualPageView();
+ }
}
run(ctx) {
this.pageView.render(ctx);
}
+
+ stop() {
+ this.pageView.unrender();
+ }
}
module.exports = new PageController();
diff --git a/client/js/controllers/settings_controller.js b/client/js/controllers/settings_controller.js
index 731ea2da..51fe8bb5 100644
--- a/client/js/controllers/settings_controller.js
+++ b/client/js/controllers/settings_controller.js
@@ -25,6 +25,7 @@ class SettingsController {
saveSettings(browsingSettings) {
localStorage.setItem('settings', JSON.stringify(browsingSettings));
events.notify(events.Success, 'Settings saved');
+ events.notify(events.SettingsChange);
}
getSettings(settings) {
diff --git a/client/js/controllers/users_controller.js b/client/js/controllers/users_controller.js
index c5046b0b..12e8899a 100644
--- a/client/js/controllers/users_controller.js
+++ b/client/js/controllers/users_controller.js
@@ -39,7 +39,14 @@ class UsersController {
'/user/:name/delete',
(ctx, next) => { this.loadUserRoute(ctx, next); },
(ctx, next) => { this.deleteUserRoute(ctx, next); });
- page.exit('/user/', (ctx, next) => { this.user = null; });
+ page.exit(/\/users\/.*/, (ctx, next) => {
+ pageController.stop();
+ next();
+ });
+ page.exit(/\/user\/.*/, (ctx, next) => {
+ this.user = null;
+ next();
+ });
}
listUsersRoute(ctx, next) {
diff --git a/client/js/events.js b/client/js/events.js
index d8532a07..16f44972 100644
--- a/client/js/events.js
+++ b/client/js/events.js
@@ -27,6 +27,7 @@ module.exports = {
Error: 2,
Info: 3,
Authentication: 4,
+ SettingsChange: 5,
notify: notify,
listen: listen,
diff --git a/client/js/util/polyfill.js b/client/js/util/polyfill.js
index de0b67b1..5e6101f7 100644
--- a/client/js/util/polyfill.js
+++ b/client/js/util/polyfill.js
@@ -29,6 +29,14 @@ if (!Object.entries) {
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// non standard
+Node.prototype.prependChild = function(child) {
+ if (this.firstChild) {
+ this.insertBefore(child, this.firstChild);
+ } else {
+ this.appendChild(child);
+ }
+};
+
Promise.prototype.always = function(onResolveOrReject) {
return this.then(
onResolveOrReject,
diff --git a/client/js/views/endless_page_view.js b/client/js/views/endless_page_view.js
new file mode 100644
index 00000000..408e3d74
--- /dev/null
+++ b/client/js/views/endless_page_view.js
@@ -0,0 +1,101 @@
+'use strict';
+
+const events = require('../events.js');
+const misc = require('../util/misc.js');
+const views = require('../util/views.js');
+
+function removeConsecutiveDuplicates(a) {
+ return a.filter((item, pos, ary) => {
+ return !pos || item != ary[pos - 1];
+ });
+}
+
+class EndlessPageView {
+ constructor() {
+ this.holderTemplate = views.getTemplate('endless-pager');
+ this.pageTemplate = views.getTemplate('endless-pager-page');
+ }
+
+ render(ctx) {
+ const target = document.getElementById('content-holder');
+ const source = this.holderTemplate();
+ const pagesHolder = source.querySelector('.pages-holder');
+ views.listenToMessages(target);
+ views.showView(target, source);
+
+ const threshold = window.innerHeight / 3;
+
+ this.minPageShown = null;
+ this.maxPageShown = null;
+ this.totalPages = null;
+ this.fetching = false;
+
+ this.updater = () => {
+ if (this.fetching || this.totalPages === null) {
+ return;
+ }
+ let scrollHeight =
+ document.documentElement.scrollHeight -
+ document.documentElement.clientHeight;
+
+ if (this.minPageShown > 1 && window.scrollY - threshold < 0) {
+ this.loadPage(pagesHolder, ctx, this.minPageShown - 1, false);
+ } else if (this.maxPageShown < this.totalPages &&
+ window.scrollY + threshold > scrollHeight) {
+ this.loadPage(pagesHolder, ctx, this.maxPageShown + 1, true);
+ }
+ };
+
+ this.loadPage(pagesHolder, ctx, ctx.initialPage, true);
+ window.addEventListener('scroll', this.updater, true);
+ }
+
+ unrender() {
+ window.removeEventListener('scroll', this.updater, true);
+ }
+
+ loadPage(pagesHolder, ctx, currentPage, append) {
+ this.fetching = true;
+
+ if (currentPage < this.minPageShown || this.minPageShown === null) {
+ this.minPageShown = currentPage;
+ }
+ if (currentPage > this.maxPageShown || this.maxPageShown === null) {
+ this.maxPageShown = currentPage;
+ }
+
+ ctx.requestPage(currentPage).then(response => {
+ this.totalPages = Math.ceil(response.total / response.pageSize);
+ if (response.total) {
+ const page = this.pageTemplate({
+ page: currentPage,
+ totalPages: this.totalPages,
+ });
+
+ let pageRendererCtx = response;
+ pageRendererCtx.target = page.querySelector(
+ '.page-content-holder');
+ ctx.pageRenderer.render(pageRendererCtx);
+
+ if (append) {
+ pagesHolder.appendChild(page);
+ } else {
+ pagesHolder.prependChild(page);
+ window.scroll(
+ window.scrollX, window.scrollY + page.offsetHeight);
+ }
+ }
+
+ this.fetching = false;
+ this.updater();
+
+ if (response.total <= (currentPage - 1) * response.pageSize) {
+ events.notify(events.Info, 'No data to show');
+ }
+ }, response => {
+ events.notify(events.Error, response.description);
+ });
+ }
+}
+
+module.exports = EndlessPageView;
diff --git a/client/js/views/manual_page_view.js b/client/js/views/manual_page_view.js
index 7a166e7e..736999f1 100644
--- a/client/js/views/manual_page_view.js
+++ b/client/js/views/manual_page_view.js
@@ -75,7 +75,7 @@ class ManualPageView {
views.listenToMessages(target);
views.showView(target, source);
- if (response.total < (currentPage - 1) * response.pageSize) {
+ if (response.total <= (currentPage - 1) * response.pageSize) {
events.notify(events.Info, 'No data to show');
}
}, response => {
@@ -84,6 +84,9 @@ class ManualPageView {
events.notify(events.Error, response.description);
});
}
+
+ unrender() {
+ }
}
module.exports = ManualPageView;