diff --git a/client/js/main.js b/client/js/main.js index d90add72..919c7ed1 100644 --- a/client/js/main.js +++ b/client/js/main.js @@ -24,12 +24,6 @@ router.enter( (ctx, next) => { mousetrap.reset(); next(); - window.requestAnimationFrame( - () => { - window.scrollTo( - ctx.state.scrollX || 0, - ctx.state.scrollY || 0); - }); }); // register controller routes diff --git a/client/js/util/views.js b/client/js/util/views.js index 9561a271..1be61736 100644 --- a/client/js/util/views.js +++ b/client/js/util/views.js @@ -390,17 +390,15 @@ function replaceContent(target, source) { } } -function scrollToHash() { - window.setTimeout(() => { - if (!window.location.hash) { - return; - } - const el = document.getElementById( - window.location.hash.replace(/#/, '')); - if (el) { - el.scrollIntoView(); - } - }, 10); +function syncScrollPosition() { + window.requestAnimationFrame( + () => { + if (history.state.hasOwnProperty('scrollX')) { + window.scrollTo(history.state.scrollX, history.state.scrollY); + } else { + window.scrollTo(0, 0); + } + }); } function slideDown(element) { @@ -470,7 +468,7 @@ module.exports = { decorateValidator: decorateValidator, makeVoidElement: makeVoidElement, makeNonVoidElement: makeNonVoidElement, - scrollToHash: scrollToHash, + syncScrollPosition: syncScrollPosition, slideDown: slideDown, slideUp: slideUp, monitorNodeRemoval: monitorNodeRemoval, diff --git a/client/js/views/empty_view.js b/client/js/views/empty_view.js index d58a5dec..21843d73 100644 --- a/client/js/views/empty_view.js +++ b/client/js/views/empty_view.js @@ -11,6 +11,7 @@ class EmptyView { constructor() { this._hostNode = document.getElementById('content-holder'); views.replaceContent(this._hostNode, template()); + views.syncScrollPosition(); } showError(message) { diff --git a/client/js/views/endless_page_view.js b/client/js/views/endless_page_view.js index 8bf6a0d6..736fa5e5 100644 --- a/client/js/views/endless_page_view.js +++ b/client/js/views/endless_page_view.js @@ -11,7 +11,7 @@ class EndlessPageView { this._hostNode = document.getElementById('content-holder'); this._active = true; this._working = 0; - this._init = true; + this._init = false; this.threshold = window.innerHeight / 3; this.minPageShown = null; @@ -30,7 +30,11 @@ class EndlessPageView { ctx.headerRenderer(ctx.headerContext); } - this._loadPage(ctx, ctx.parameters.page, true); + this._loadPage(ctx, ctx.parameters.page, true).then(pageNode => { + if (ctx.parameters.page !== 1) { + pageNode.scrollIntoView(); + } + }); this._probePageLoad(ctx); } @@ -87,26 +91,32 @@ class EndlessPageView { _loadPage(ctx, pageNumber, append) { this._working++; - ctx.requestPage(pageNumber).then(response => { - if (!this._active) { - this._working--; - return Promise.reject(); - } - this.totalPages = Math.ceil(response.total / response.pageSize); - window.requestAnimationFrame(() => { - this._renderPage( - ctx, pageNumber, append, response); + return new Promise((resolve, reject) => { + ctx.requestPage(pageNumber).then(response => { + if (!this._active) { + this._working--; + return Promise.reject(); + } + this.totalPages = Math.ceil(response.total / response.pageSize); + window.requestAnimationFrame(() => { + let pageNode = this._renderPage( + ctx, pageNumber, append, response); + this._working--; + resolve(pageNode); + }); + }, response => { + this.showError(response.description); this._working--; + reject(); }); - }, response => { - this.showError(response.description); - this._working--; }); } _renderPage(ctx, pageNumber, append, response) { + let pageNode = null; + if (response.total) { - const pageNode = pageTemplate({ + pageNode = pageTemplate({ page: pageNumber, totalPages: this.totalPages, }); @@ -128,7 +138,7 @@ class EndlessPageView { if (append) { this._pagesHolderNode.appendChild(pageNode); - if (this._init && pageNumber !== 1) { + if (!this._init && pageNumber !== 1) { window.scroll(0, pageNode.getBoundingClientRect().top); } } else { @@ -141,7 +151,9 @@ class EndlessPageView { } else if (response.total <= (pageNumber - 1) * response.pageSize) { this.showInfo('No data to show'); } - this._init = false; + + this._init = true; + return pageNode; } showSuccess(message) { diff --git a/client/js/views/help_view.js b/client/js/views/help_view.js index e8747acb..61a13300 100644 --- a/client/js/views/help_view.js +++ b/client/js/views/help_view.js @@ -59,7 +59,7 @@ class HelpView { } views.replaceContent(this._hostNode, sourceNode); - views.scrollToHash(); + views.syncScrollPosition(); } } diff --git a/client/js/views/home_view.js b/client/js/views/home_view.js index 32789721..af3d2ddc 100644 --- a/client/js/views/home_view.js +++ b/client/js/views/home_view.js @@ -20,6 +20,7 @@ class HomeView { const sourceNode = template(ctx); views.replaceContent(this._hostNode, sourceNode); + views.syncScrollPosition(); if (this._formNode) { this._tagAutoCompleteControl = new TagAutoCompleteControl( diff --git a/client/js/views/login_view.js b/client/js/views/login_view.js index a19a0cc8..96d6d7d0 100644 --- a/client/js/views/login_view.js +++ b/client/js/views/login_view.js @@ -16,6 +16,7 @@ class LoginView extends events.EventTarget { passwordPattern: config.passwordRegex, canSendMails: config.canSendMails, })); + views.syncScrollPosition(); views.decorateValidator(this._formNode); this._userNameFieldNode.setAttribute('pattern', config.userNameRegex); diff --git a/client/js/views/manual_page_view.js b/client/js/views/manual_page_view.js index 1ceac67e..c4ce34f4 100644 --- a/client/js/views/manual_page_view.js +++ b/client/js/views/manual_page_view.js @@ -107,6 +107,8 @@ class ManualPageView { if (response.total <= (currentPage - 1) * response.pageSize) { this.showInfo('No data to show'); } + + views.syncScrollPosition(); }, response => { this.showError(response.description); }); diff --git a/client/js/views/not_found_view.js b/client/js/views/not_found_view.js index bb60280c..8b5a1039 100644 --- a/client/js/views/not_found_view.js +++ b/client/js/views/not_found_view.js @@ -11,6 +11,7 @@ class NotFoundView { const sourceNode = template({path: path}); views.replaceContent(this._hostNode, sourceNode); + views.syncScrollPosition(); } } diff --git a/client/js/views/password_reset_view.js b/client/js/views/password_reset_view.js index 73e782d8..ddb9785e 100644 --- a/client/js/views/password_reset_view.js +++ b/client/js/views/password_reset_view.js @@ -11,8 +11,9 @@ class PasswordResetView extends events.EventTarget { this._hostNode = document.getElementById('content-holder'); views.replaceContent(this._hostNode, template()); - views.decorateValidator(this._formNode); + views.syncScrollPosition(); + views.decorateValidator(this._formNode); this._hostNode.addEventListener('submit', e => { e.preventDefault(); this.dispatchEvent(new CustomEvent('submit', { diff --git a/client/js/views/post_view.js b/client/js/views/post_view.js index 0f5d6e56..37bf4394 100644 --- a/client/js/views/post_view.js +++ b/client/js/views/post_view.js @@ -23,6 +23,7 @@ class PostView { const postContainerNode = sourceNode.querySelector('.post-container'); const sidebarNode = sourceNode.querySelector('.sidebar'); views.replaceContent(this._hostNode, sourceNode); + views.syncScrollPosition(); const postViewNode = document.body.querySelector('.content-wrapper'); const topNavigationNode = diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index f8d56b35..d7405144 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -14,6 +14,7 @@ class RegistrationView extends events.EventTarget { userNamePattern: config.userNameRegex, passwordPattern: config.passwordRegex, })); + views.syncScrollPosition(); views.decorateValidator(this._formNode); this._formNode.addEventListener('submit', e => this._evtSubmit(e)); } diff --git a/client/js/views/settings_view.js b/client/js/views/settings_view.js index c1cbe1de..137c20d8 100644 --- a/client/js/views/settings_view.js +++ b/client/js/views/settings_view.js @@ -12,8 +12,9 @@ class SettingsView extends events.EventTarget { this._hostNode = document.getElementById('content-holder'); views.replaceContent( this._hostNode, template({browsingSettings: ctx.settings})); - views.decorateValidator(this._formNode); + views.syncScrollPosition(); + views.decorateValidator(this._formNode); this._formNode.addEventListener('submit', e => this._evtSubmit(e)); } diff --git a/client/js/views/tag_categories_view.js b/client/js/views/tag_categories_view.js index ac97db20..51e3d390 100644 --- a/client/js/views/tag_categories_view.js +++ b/client/js/views/tag_categories_view.js @@ -14,6 +14,7 @@ class TagCategoriesView extends events.EventTarget { this._hostNode = document.getElementById('content-holder'); views.replaceContent(this._hostNode, template(ctx)); + views.syncScrollPosition(); views.decorateValidator(this._formNode); const categoriesToAdd = Array.from(ctx.tagCategories); diff --git a/client/js/views/tag_view.js b/client/js/views/tag_view.js index e43c1c1c..db90dc5f 100644 --- a/client/js/views/tag_view.js +++ b/client/js/views/tag_view.js @@ -52,6 +52,8 @@ class TagView extends events.EventTarget { } else { this._view = new TagSummaryView(ctx); } + + views.syncScrollPosition(); } clearMessages() { diff --git a/client/js/views/user_view.js b/client/js/views/user_view.js index 6411c7ef..cb86820b 100644 --- a/client/js/views/user_view.js +++ b/client/js/views/user_view.js @@ -47,6 +47,8 @@ class UserView extends events.EventTarget { } else { this._view = new UserSummaryView(ctx); } + + views.syncScrollPosition(); } clearMessages() {