client/paging: avoid redrawing header navigation
This commit is contained in:
parent
e83e1b06a1
commit
cf1d15354d
16 changed files with 263 additions and 164 deletions
|
@ -127,6 +127,11 @@
|
||||||
.start-tagging,
|
.start-tagging,
|
||||||
.stop-tagging
|
.stop-tagging
|
||||||
display: none
|
display: none
|
||||||
|
.masstag-hint
|
||||||
|
display: none
|
||||||
|
&.active
|
||||||
|
.open-masstag
|
||||||
|
display: none
|
||||||
|
|
||||||
.safety
|
.safety
|
||||||
margin-right: 0.25em
|
margin-right: 0.25em
|
||||||
|
|
|
@ -12,11 +12,8 @@
|
||||||
%><% if (ctx.canMassTag) { %><%
|
%><% if (ctx.canMassTag) { %><%
|
||||||
%><wbr/><%
|
%><wbr/><%
|
||||||
%><span class='masstag'><%
|
%><span class='masstag'><%
|
||||||
%><% if (ctx.parameters.tag) { %><%
|
%><span class='append masstag-hint'>Tagging with:</span><%
|
||||||
%><span class='append masstag-hint'>Tagging with:</span><%
|
%><a href class='mousetrap button append open-masstag'>Mass tag</a><%
|
||||||
%><% } else { %><%
|
|
||||||
%><a href class='mousetrap button append open-masstag'>Mass tag</a><%
|
|
||||||
%><% } %><%
|
|
||||||
%><wbr/><%
|
%><wbr/><%
|
||||||
%><%= ctx.makeTextInput({name: 'masstag', value: ctx.parameters.tag}) %><%
|
%><%= ctx.makeTextInput({name: 'masstag', value: ctx.parameters.tag}) %><%
|
||||||
%><input class='mousetrap start-tagging' type='submit' value='Start tagging'/><%
|
%><input class='mousetrap start-tagging' type='submit' value='Start tagging'/><%
|
||||||
|
|
|
@ -22,7 +22,8 @@ class CommentsController {
|
||||||
topNavigation.activate('comments');
|
topNavigation.activate('comments');
|
||||||
topNavigation.setTitle('Listing comments');
|
topNavigation.setTitle('Listing comments');
|
||||||
|
|
||||||
this._pageController = new PageController({
|
this._pageController = new PageController();
|
||||||
|
this._pageController.run({
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
getClientUrlForPage: page => {
|
getClientUrlForPage: page => {
|
||||||
const parameters = Object.assign(
|
const parameters = Object.assign(
|
||||||
|
|
|
@ -6,19 +6,25 @@ const ManualPageView = require('../views/manual_page_view.js');
|
||||||
|
|
||||||
class PageController {
|
class PageController {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
if (settings.get().endlessScroll) {
|
||||||
|
this._view = new EndlessPageView();
|
||||||
|
} else {
|
||||||
|
this._view = new ManualPageView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get view() {
|
||||||
|
return this._view;
|
||||||
|
}
|
||||||
|
|
||||||
|
run(ctx) {
|
||||||
const extendedContext = {
|
const extendedContext = {
|
||||||
getClientUrlForPage: ctx.getClientUrlForPage,
|
getClientUrlForPage: ctx.getClientUrlForPage,
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.headerContext = Object.assign({}, extendedContext);
|
|
||||||
ctx.pageContext = Object.assign({}, extendedContext);
|
ctx.pageContext = Object.assign({}, extendedContext);
|
||||||
|
this._view.run(ctx);
|
||||||
if (settings.get().endlessScroll) {
|
|
||||||
this._view = new EndlessPageView(ctx);
|
|
||||||
} else {
|
|
||||||
this._view = new ManualPageView(ctx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccess(message) {
|
showSuccess(message) {
|
||||||
|
|
|
@ -26,37 +26,18 @@ class PostListController {
|
||||||
topNavigation.setTitle('Listing posts');
|
topNavigation.setTitle('Listing posts');
|
||||||
|
|
||||||
this._ctx = ctx;
|
this._ctx = ctx;
|
||||||
this._pageController = new PageController({
|
this._pageController = new PageController();
|
||||||
|
|
||||||
|
this._headerView = new PostsHeaderView({
|
||||||
|
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
getClientUrlForPage: page => {
|
canMassTag: api.hasPrivilege('tags:masstag'),
|
||||||
const parameters = Object.assign(
|
massTagTags: this._massTagTags,
|
||||||
{}, ctx.parameters, {page: page});
|
|
||||||
return '/posts/' + misc.formatUrlParameters(parameters);
|
|
||||||
},
|
|
||||||
requestPage: page => {
|
|
||||||
return PostList.search(
|
|
||||||
this._decorateSearchQuery(ctx.parameters.query),
|
|
||||||
page, settings.get().postsPerPage, fields);
|
|
||||||
},
|
|
||||||
headerRenderer: headerCtx => {
|
|
||||||
Object.assign(headerCtx, {
|
|
||||||
canMassTag: api.hasPrivilege('tags:masstag'),
|
|
||||||
massTagTags: this._massTagTags,
|
|
||||||
});
|
|
||||||
return new PostsHeaderView(headerCtx);
|
|
||||||
},
|
|
||||||
pageRenderer: pageCtx => {
|
|
||||||
Object.assign(pageCtx, {
|
|
||||||
canViewPosts: api.hasPrivilege('posts:view'),
|
|
||||||
canMassTag: api.hasPrivilege('tags:masstag'),
|
|
||||||
massTagTags: this._massTagTags,
|
|
||||||
});
|
|
||||||
const view = new PostsPageView(pageCtx);
|
|
||||||
view.addEventListener('tag', e => this._evtTag(e));
|
|
||||||
view.addEventListener('untag', e => this._evtUntag(e));
|
|
||||||
return view;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
this._headerView.addEventListener(
|
||||||
|
'navigate', e => this._evtNavigate(e));
|
||||||
|
|
||||||
|
this._syncPageController();
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccess(message) {
|
showSuccess(message) {
|
||||||
|
@ -67,6 +48,15 @@ class PostListController {
|
||||||
return (this._ctx.parameters.tag || '').split(/\s+/).filter(s => s);
|
return (this._ctx.parameters.tag || '').split(/\s+/).filter(s => s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_evtNavigate(e) {
|
||||||
|
history.pushState(
|
||||||
|
null,
|
||||||
|
window.title,
|
||||||
|
'/posts/' + misc.formatUrlParameters(e.detail.parameters));
|
||||||
|
Object.assign(this._ctx.parameters, e.detail.parameters);
|
||||||
|
this._syncPageController();
|
||||||
|
}
|
||||||
|
|
||||||
_evtTag(e) {
|
_evtTag(e) {
|
||||||
for (let tag of this._massTagTags) {
|
for (let tag of this._massTagTags) {
|
||||||
e.detail.post.addTag(tag);
|
e.detail.post.addTag(tag);
|
||||||
|
@ -100,6 +90,32 @@ class PostListController {
|
||||||
}
|
}
|
||||||
return text.trim();
|
return text.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_syncPageController() {
|
||||||
|
this._pageController.run({
|
||||||
|
parameters: this._ctx.parameters,
|
||||||
|
getClientUrlForPage: page => {
|
||||||
|
return '/posts/' + misc.formatUrlParameters(
|
||||||
|
Object.assign({}, this._ctx.parameters, {page: page}));
|
||||||
|
},
|
||||||
|
requestPage: page => {
|
||||||
|
return PostList.search(
|
||||||
|
this._decorateSearchQuery(this._ctx.parameters.query),
|
||||||
|
page, settings.get().postsPerPage, fields);
|
||||||
|
},
|
||||||
|
pageRenderer: pageCtx => {
|
||||||
|
Object.assign(pageCtx, {
|
||||||
|
canViewPosts: api.hasPrivilege('posts:view'),
|
||||||
|
canMassTag: api.hasPrivilege('tags:masstag'),
|
||||||
|
massTagTags: this._massTagTags,
|
||||||
|
});
|
||||||
|
const view = new PostsPageView(pageCtx);
|
||||||
|
view.addEventListener('tag', e => this._evtTag(e));
|
||||||
|
view.addEventListener('untag', e => this._evtUntag(e));
|
||||||
|
return view;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = router => {
|
module.exports = router => {
|
||||||
|
|
|
@ -19,7 +19,8 @@ class SnapshotsController {
|
||||||
topNavigation.activate('');
|
topNavigation.activate('');
|
||||||
topNavigation.setTitle('History');
|
topNavigation.setTitle('History');
|
||||||
|
|
||||||
this._pageController = new PageController({
|
this._pageController = new PageController();
|
||||||
|
this._pageController.run({
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
getClientUrlForPage: page => {
|
getClientUrlForPage: page => {
|
||||||
const parameters = Object.assign(
|
const parameters = Object.assign(
|
||||||
|
|
|
@ -23,27 +23,18 @@ class TagListController {
|
||||||
topNavigation.activate('tags');
|
topNavigation.activate('tags');
|
||||||
topNavigation.setTitle('Listing tags');
|
topNavigation.setTitle('Listing tags');
|
||||||
|
|
||||||
this._pageController = new PageController({
|
this._ctx = ctx;
|
||||||
|
this._pageController = new PageController();
|
||||||
|
|
||||||
|
this._headerView = new TagsHeaderView({
|
||||||
|
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
getClientUrlForPage: page => {
|
canEditTagCategories: api.hasPrivilege('tagCategories:edit'),
|
||||||
const parameters = Object.assign(
|
|
||||||
{}, ctx.parameters, {page: page});
|
|
||||||
return '/tags/' + misc.formatUrlParameters(parameters);
|
|
||||||
},
|
|
||||||
requestPage: page => {
|
|
||||||
return TagList.search(ctx.parameters.query, page, 50, fields);
|
|
||||||
},
|
|
||||||
headerRenderer: headerCtx => {
|
|
||||||
Object.assign(headerCtx, {
|
|
||||||
canEditTagCategories:
|
|
||||||
api.hasPrivilege('tagCategories:edit'),
|
|
||||||
});
|
|
||||||
return new TagsHeaderView(headerCtx);
|
|
||||||
},
|
|
||||||
pageRenderer: pageCtx => {
|
|
||||||
return new TagsPageView(pageCtx);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
this._headerView.addEventListener(
|
||||||
|
'navigate', e => this._evtNavigate(e));
|
||||||
|
|
||||||
|
this._syncPageController();
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccess(message) {
|
showSuccess(message) {
|
||||||
|
@ -53,6 +44,33 @@ class TagListController {
|
||||||
showError(message) {
|
showError(message) {
|
||||||
this._pageController.showError(message);
|
this._pageController.showError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_evtNavigate(e) {
|
||||||
|
history.pushState(
|
||||||
|
null,
|
||||||
|
window.title,
|
||||||
|
'/tags/' + misc.formatUrlParameters(e.detail.parameters));
|
||||||
|
Object.assign(this._ctx.parameters, e.detail.parameters);
|
||||||
|
this._syncPageController();
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncPageController() {
|
||||||
|
this._pageController.run({
|
||||||
|
parameters: this._ctx.parameters,
|
||||||
|
getClientUrlForPage: page => {
|
||||||
|
const parameters = Object.assign(
|
||||||
|
{}, this._ctx.parameters, {page: page});
|
||||||
|
return '/tags/' + misc.formatUrlParameters(parameters);
|
||||||
|
},
|
||||||
|
requestPage: page => {
|
||||||
|
return TagList.search(
|
||||||
|
this._ctx.parameters.query, page, 50, fields);
|
||||||
|
},
|
||||||
|
pageRenderer: pageCtx => {
|
||||||
|
return new TagsPageView(pageCtx);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = router => {
|
module.exports = router => {
|
||||||
|
|
|
@ -20,18 +20,42 @@ class UserListController {
|
||||||
topNavigation.activate('users');
|
topNavigation.activate('users');
|
||||||
topNavigation.setTitle('Listing users');
|
topNavigation.setTitle('Listing users');
|
||||||
|
|
||||||
this._pageController = new PageController({
|
this._ctx = ctx;
|
||||||
|
this._pageController = new PageController();
|
||||||
|
|
||||||
|
this._headerView = new UsersHeaderView({
|
||||||
|
hostNode: this._pageController.view.pageHeaderHolderNode,
|
||||||
parameters: ctx.parameters,
|
parameters: ctx.parameters,
|
||||||
|
});
|
||||||
|
this._headerView.addEventListener(
|
||||||
|
'navigate', e => this._evtNavigate(e));
|
||||||
|
|
||||||
|
this._syncPageController();
|
||||||
|
}
|
||||||
|
|
||||||
|
showSuccess(message) {
|
||||||
|
this._pageController.showSuccess(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_evtNavigate(e) {
|
||||||
|
history.pushState(
|
||||||
|
null,
|
||||||
|
window.title,
|
||||||
|
'/users/' + misc.formatUrlParameters(e.detail.parameters));
|
||||||
|
Object.assign(this._ctx.parameters, e.detail.parameters);
|
||||||
|
this._syncPageController();
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncPageController() {
|
||||||
|
this._pageController.run({
|
||||||
|
parameters: this._ctx.parameters,
|
||||||
getClientUrlForPage: page => {
|
getClientUrlForPage: page => {
|
||||||
const parameters = Object.assign(
|
const parameters = Object.assign(
|
||||||
{}, ctx.parameters, {page: page});
|
{}, this._ctx.parameters, {page: page});
|
||||||
return '/users/' + misc.formatUrlParameters(parameters);
|
return '/users/' + misc.formatUrlParameters(parameters);
|
||||||
},
|
},
|
||||||
requestPage: page => {
|
requestPage: page => {
|
||||||
return UserList.search(ctx.parameters.query, page);
|
return UserList.search(this._ctx.parameters.query, page);
|
||||||
},
|
|
||||||
headerRenderer: headerCtx => {
|
|
||||||
return new UsersHeaderView(headerCtx);
|
|
||||||
},
|
},
|
||||||
pageRenderer: pageCtx => {
|
pageRenderer: pageCtx => {
|
||||||
Object.assign(pageCtx, {
|
Object.assign(pageCtx, {
|
||||||
|
@ -41,10 +65,6 @@ class UserListController {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccess(message) {
|
|
||||||
this._pageController.showSuccess(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = router => {
|
module.exports = router => {
|
||||||
|
|
|
@ -137,8 +137,8 @@ class Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
show(path, state, push) {
|
show(path, state, push) {
|
||||||
const oldPath = this.ctx ? this.ctx.path : ctx.path;
|
|
||||||
const ctx = new Context(path, state);
|
const ctx = new Context(path, state);
|
||||||
|
const oldPath = this.ctx ? this.ctx.path : ctx.path;
|
||||||
this.dispatch(ctx, () => {
|
this.dispatch(ctx, () => {
|
||||||
if (ctx.path !== oldPath && push !== false) {
|
if (ctx.path !== oldPath && push !== false) {
|
||||||
ctx.pushState();
|
ctx.pushState();
|
||||||
|
|
17
client/js/util/search.js
Normal file
17
client/js/util/search.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const misc = require('./misc.js');
|
||||||
|
const keyboard = require('../util/keyboard.js');
|
||||||
|
const views = require('./views.js');
|
||||||
|
|
||||||
|
function searchInputNodeFocusHelper(inputNode) {
|
||||||
|
keyboard.bind('q', () => {
|
||||||
|
inputNode.focus();
|
||||||
|
inputNode.setSelectionRange(
|
||||||
|
inputNode.value.length, inputNode.value.length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = misc.arrayToObject([
|
||||||
|
searchInputNodeFocusHelper,
|
||||||
|
], func => func.name);
|
|
@ -248,6 +248,25 @@ function makeVoidElement(name, attributes) {
|
||||||
return `<${_serializeElement(name, attributes)}/>`;
|
return `<${_serializeElement(name, attributes)}/>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emptyContent(target) {
|
||||||
|
while (target.lastChild) {
|
||||||
|
target.removeChild(target.lastChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceContent(target, source) {
|
||||||
|
emptyContent(target);
|
||||||
|
if (source instanceof NodeList) {
|
||||||
|
for (let child of [...source]) {
|
||||||
|
target.appendChild(child);
|
||||||
|
}
|
||||||
|
} else if (source instanceof Node) {
|
||||||
|
target.appendChild(source);
|
||||||
|
} else if (source !== null) {
|
||||||
|
throw `Invalid view source: ${source}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showMessage(target, message, className) {
|
function showMessage(target, message, className) {
|
||||||
if (!message) {
|
if (!message) {
|
||||||
message = 'Unknown message';
|
message = 'Unknown message';
|
||||||
|
@ -283,9 +302,7 @@ function showInfo(target, message) {
|
||||||
function clearMessages(target) {
|
function clearMessages(target) {
|
||||||
const messagesHolder = target.querySelector('.messages');
|
const messagesHolder = target.querySelector('.messages');
|
||||||
/* TODO: animate that */
|
/* TODO: animate that */
|
||||||
while (messagesHolder.lastChild) {
|
emptyContent(messagesHolder);
|
||||||
messagesHolder.removeChild(messagesHolder.lastChild);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function htmlToDom(html) {
|
function htmlToDom(html) {
|
||||||
|
@ -394,25 +411,10 @@ function enableForm(form) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceContent(target, source) {
|
|
||||||
while (target.lastChild) {
|
|
||||||
target.removeChild(target.lastChild);
|
|
||||||
}
|
|
||||||
if (source instanceof NodeList) {
|
|
||||||
for (let child of [...source]) {
|
|
||||||
target.appendChild(child);
|
|
||||||
}
|
|
||||||
} else if (source instanceof Node) {
|
|
||||||
target.appendChild(source);
|
|
||||||
} else if (source !== null) {
|
|
||||||
throw `Invalid view source: ${source}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncScrollPosition() {
|
function syncScrollPosition() {
|
||||||
window.requestAnimationFrame(
|
window.requestAnimationFrame(
|
||||||
() => {
|
() => {
|
||||||
if (history.state.hasOwnProperty('scrollX')) {
|
if (history.state && history.state.hasOwnProperty('scrollX')) {
|
||||||
window.scrollTo(history.state.scrollX, history.state.scrollY);
|
window.scrollTo(history.state.scrollX, history.state.scrollY);
|
||||||
} else {
|
} else {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
|
@ -488,6 +490,7 @@ document.addEventListener('click', e => {
|
||||||
module.exports = misc.arrayToObject([
|
module.exports = misc.arrayToObject([
|
||||||
htmlToDom,
|
htmlToDom,
|
||||||
getTemplate,
|
getTemplate,
|
||||||
|
emptyContent,
|
||||||
replaceContent,
|
replaceContent,
|
||||||
enableForm,
|
enableForm,
|
||||||
disableForm,
|
disableForm,
|
||||||
|
|
|
@ -9,27 +9,22 @@ const pageTemplate = views.getTemplate('endless-pager-page');
|
||||||
class EndlessPageView {
|
class EndlessPageView {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
this._hostNode = document.getElementById('content-holder');
|
this._hostNode = document.getElementById('content-holder');
|
||||||
|
views.replaceContent(this._hostNode, holderTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
|
run(ctx) {
|
||||||
this._active = true;
|
this._active = true;
|
||||||
this._working = 0;
|
this._working = 0;
|
||||||
this._init = false;
|
this._init = false;
|
||||||
|
|
||||||
|
views.emptyContent(this._pagesHolderNode);
|
||||||
|
|
||||||
this.threshold = window.innerHeight / 3;
|
this.threshold = window.innerHeight / 3;
|
||||||
this.minPageShown = null;
|
this.minPageShown = null;
|
||||||
this.maxPageShown = null;
|
this.maxPageShown = null;
|
||||||
this.totalPages = null;
|
this.totalPages = null;
|
||||||
this.currentPage = null;
|
this.currentPage = null;
|
||||||
|
|
||||||
const sourceNode = holderTemplate();
|
|
||||||
const pageHeaderHolderNode
|
|
||||||
= sourceNode.querySelector('.page-header-holder');
|
|
||||||
this._pagesHolderNode = sourceNode.querySelector('.pages-holder');
|
|
||||||
views.replaceContent(this._hostNode, sourceNode);
|
|
||||||
|
|
||||||
ctx.headerContext.hostNode = pageHeaderHolderNode;
|
|
||||||
if (ctx.headerRenderer) {
|
|
||||||
ctx.headerRenderer(ctx.headerContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._loadPage(ctx, ctx.parameters.page, true).then(pageNode => {
|
this._loadPage(ctx, ctx.parameters.page, true).then(pageNode => {
|
||||||
if (ctx.parameters.page !== 1) {
|
if (ctx.parameters.page !== 1) {
|
||||||
pageNode.scrollIntoView();
|
pageNode.scrollIntoView();
|
||||||
|
@ -40,6 +35,14 @@ class EndlessPageView {
|
||||||
views.monitorNodeRemoval(this._pagesHolderNode, () => this._destroy());
|
views.monitorNodeRemoval(this._pagesHolderNode, () => this._destroy());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get pageHeaderHolderNode() {
|
||||||
|
return this._hostNode.querySelector('.page-header-holder');
|
||||||
|
}
|
||||||
|
|
||||||
|
get _pagesHolderNode() {
|
||||||
|
return this._hostNode.querySelector('.pages-holder');
|
||||||
|
}
|
||||||
|
|
||||||
_destroy() {
|
_destroy() {
|
||||||
this._active = false;
|
this._active = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,25 +56,17 @@ function _getPages(currentPage, pageNumbers, ctx) {
|
||||||
class ManualPageView {
|
class ManualPageView {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
this._hostNode = document.getElementById('content-holder');
|
this._hostNode = document.getElementById('content-holder');
|
||||||
|
views.replaceContent(this._hostNode, holderTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
const sourceNode = holderTemplate();
|
run(ctx) {
|
||||||
const pageContentHolderNode
|
|
||||||
= sourceNode.querySelector('.page-content-holder');
|
|
||||||
const pageHeaderHolderNode
|
|
||||||
= sourceNode.querySelector('.page-header-holder');
|
|
||||||
const pageNavNode = sourceNode.querySelector('.page-nav');
|
|
||||||
const currentPage = ctx.parameters.page;
|
const currentPage = ctx.parameters.page;
|
||||||
|
this.clearMessages();
|
||||||
ctx.headerContext.hostNode = pageHeaderHolderNode;
|
views.emptyContent(this._pageNavNode);
|
||||||
if (ctx.headerRenderer) {
|
|
||||||
ctx.headerRenderer(ctx.headerContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
views.replaceContent(this._hostNode, sourceNode);
|
|
||||||
|
|
||||||
ctx.requestPage(currentPage).then(response => {
|
ctx.requestPage(currentPage).then(response => {
|
||||||
Object.assign(ctx.pageContext, response);
|
Object.assign(ctx.pageContext, response);
|
||||||
ctx.pageContext.hostNode = pageContentHolderNode;
|
ctx.pageContext.hostNode = this._pageContentHolderNode;
|
||||||
ctx.pageRenderer(ctx.pageContext);
|
ctx.pageRenderer(ctx.pageContext);
|
||||||
|
|
||||||
const totalPages = Math.ceil(response.total / response.pageSize);
|
const totalPages = Math.ceil(response.total / response.pageSize);
|
||||||
|
@ -94,7 +86,7 @@ class ManualPageView {
|
||||||
|
|
||||||
if (response.total) {
|
if (response.total) {
|
||||||
views.replaceContent(
|
views.replaceContent(
|
||||||
pageNavNode,
|
this._pageNavNode,
|
||||||
navTemplate({
|
navTemplate({
|
||||||
prevLink: ctx.getClientUrlForPage(currentPage - 1),
|
prevLink: ctx.getClientUrlForPage(currentPage - 1),
|
||||||
nextLink: ctx.getClientUrlForPage(currentPage + 1),
|
nextLink: ctx.getClientUrlForPage(currentPage + 1),
|
||||||
|
@ -114,6 +106,22 @@ class ManualPageView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get pageHeaderHolderNode() {
|
||||||
|
return this._hostNode.querySelector('.page-header-holder');
|
||||||
|
}
|
||||||
|
|
||||||
|
get _pageContentHolderNode() {
|
||||||
|
return this._hostNode.querySelector('.page-content-holder');
|
||||||
|
}
|
||||||
|
|
||||||
|
get _pageNavNode() {
|
||||||
|
return this._hostNode.querySelector('.page-nav');
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessages() {
|
||||||
|
views.clearMessages(this._hostNode);
|
||||||
|
}
|
||||||
|
|
||||||
showSuccess(message) {
|
showSuccess(message) {
|
||||||
views.showSuccess(this._hostNode, message);
|
views.showSuccess(this._hostNode, message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,34 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const router = require('../router.js');
|
const events = require('../events.js');
|
||||||
const settings = require('../models/settings.js');
|
const settings = require('../models/settings.js');
|
||||||
const keyboard = require('../util/keyboard.js');
|
const keyboard = require('../util/keyboard.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
|
const search = require('../util/search.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
const TagAutoCompleteControl =
|
const TagAutoCompleteControl =
|
||||||
require('../controls/tag_auto_complete_control.js');
|
require('../controls/tag_auto_complete_control.js');
|
||||||
|
|
||||||
const template = views.getTemplate('posts-header');
|
const template = views.getTemplate('posts-header');
|
||||||
|
|
||||||
class PostsHeaderView {
|
class PostsHeaderView extends events.EventTarget {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
super();
|
||||||
|
|
||||||
ctx.settings = settings.get();
|
ctx.settings = settings.get();
|
||||||
this._ctx = ctx;
|
this._ctx = ctx;
|
||||||
this._hostNode = ctx.hostNode;
|
this._hostNode = ctx.hostNode;
|
||||||
views.replaceContent(this._hostNode, template(ctx));
|
views.replaceContent(this._hostNode, template(ctx));
|
||||||
|
|
||||||
if (this._queryInputNode) {
|
this._queryAutoCompleteControl = new TagAutoCompleteControl(
|
||||||
new TagAutoCompleteControl(this._queryInputNode, {addSpace: true});
|
this._queryInputNode, {addSpace: true});
|
||||||
}
|
|
||||||
if (this._massTagInputNode) {
|
if (this._massTagInputNode) {
|
||||||
new TagAutoCompleteControl(
|
this._masstagAutoCompleteControl = new TagAutoCompleteControl(
|
||||||
this._massTagInputNode, {addSpace: false});
|
this._massTagInputNode, {addSpace: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard.bind('q', () => {
|
keyboard.bind('p', () => this._focusFirstPostNode());
|
||||||
this._formNode.querySelector('input:first-of-type').focus();
|
search.searchInputNodeFocusHelper(this._queryInputNode);
|
||||||
});
|
|
||||||
|
|
||||||
keyboard.bind('p', () => {
|
|
||||||
const firstPostNode =
|
|
||||||
document.body.querySelector('.post-list li:first-child a');
|
|
||||||
if (firstPostNode) {
|
|
||||||
firstPostNode.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let safetyButtonNode of this._safetyButtonNodes) {
|
for (let safetyButtonNode of this._safetyButtonNodes) {
|
||||||
safetyButtonNode.addEventListener(
|
safetyButtonNode.addEventListener(
|
||||||
|
@ -51,8 +44,6 @@ class PostsHeaderView {
|
||||||
}
|
}
|
||||||
this._stopMassTagLinkNode.addEventListener(
|
this._stopMassTagLinkNode.addEventListener(
|
||||||
'click', e => this._evtStopTaggingClick(e));
|
'click', e => this._evtStopTaggingClick(e));
|
||||||
// this._massTagFormNode.addEventListener(
|
|
||||||
// 'submit', e => this._evtMassTagFormSubmit(e));
|
|
||||||
this._toggleMassTagVisibility(!!ctx.parameters.tag);
|
this._toggleMassTagVisibility(!!ctx.parameters.tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,10 +84,11 @@ class PostsHeaderView {
|
||||||
|
|
||||||
_evtStopTaggingClick(e) {
|
_evtStopTaggingClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
router.show('/posts/' + misc.formatUrlParameters({
|
this._toggleMassTagVisibility(false);
|
||||||
|
this.dispatchEvent(new CustomEvent('navigate', {detail: {parameters: {
|
||||||
query: this._ctx.parameters.query,
|
query: this._ctx.parameters.query,
|
||||||
page: this._ctx.parameters.page,
|
page: this._ctx.parameters.page,
|
||||||
}));
|
}}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtSafetyButtonClick(e, url) {
|
_evtSafetyButtonClick(e, url) {
|
||||||
|
@ -107,20 +99,35 @@ class PostsHeaderView {
|
||||||
browsingSettings.listPosts[safety] =
|
browsingSettings.listPosts[safety] =
|
||||||
!browsingSettings.listPosts[safety];
|
!browsingSettings.listPosts[safety];
|
||||||
settings.save(browsingSettings, true);
|
settings.save(browsingSettings, true);
|
||||||
router.show(router.url);
|
this.dispatchEvent(
|
||||||
|
new CustomEvent(
|
||||||
|
'navigate', {detail: {parameters: this._ctx.parameters}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtFormSubmit(e) {
|
_evtFormSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let params = {
|
this._queryAutoCompleteControl.hide();
|
||||||
|
if (this._masstagAutoCompleteControl) {
|
||||||
|
this._masstagAutoCompleteControl.hide();
|
||||||
|
}
|
||||||
|
let parameters = {
|
||||||
query: this._queryInputNode.value,
|
query: this._queryInputNode.value,
|
||||||
page: this._ctx.parameters.page,
|
page: this._ctx.parameters.page,
|
||||||
};
|
};
|
||||||
if (this._massTagInputNode) {
|
if (this._massTagInputNode) {
|
||||||
params.tag = this._massTagInputNode.value;
|
parameters.tag = this._massTagInputNode.value;
|
||||||
this._massTagInputNode.blur();
|
this._massTagInputNode.blur();
|
||||||
}
|
}
|
||||||
router.show('/posts/' + misc.formatUrlParameters(params));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('navigate', {detail: {parameters: parameters}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_focusFirstPostNode() {
|
||||||
|
const firstPostNode =
|
||||||
|
document.body.querySelector('.post-list li:first-child a');
|
||||||
|
if (firstPostNode) {
|
||||||
|
firstPostNode.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const router = require('../router.js');
|
const events = require('../events.js');
|
||||||
const keyboard = require('../util/keyboard.js');
|
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
|
const search = require('../util/search.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
const TagAutoCompleteControl =
|
const TagAutoCompleteControl =
|
||||||
require('../controls/tag_auto_complete_control.js');
|
require('../controls/tag_auto_complete_control.js');
|
||||||
|
|
||||||
const template = views.getTemplate('tags-header');
|
const template = views.getTemplate('tags-header');
|
||||||
|
|
||||||
class TagsHeaderView {
|
class TagsHeaderView extends events.EventTarget {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
super();
|
||||||
|
|
||||||
this._hostNode = ctx.hostNode;
|
this._hostNode = ctx.hostNode;
|
||||||
views.replaceContent(this._hostNode, template(ctx));
|
views.replaceContent(this._hostNode, template(ctx));
|
||||||
|
|
||||||
|
@ -18,9 +20,7 @@ class TagsHeaderView {
|
||||||
new TagAutoCompleteControl(this._queryInputNode);
|
new TagAutoCompleteControl(this._queryInputNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard.bind('q', () => {
|
search.searchInputNodeFocusHelper(this._queryInputNode);
|
||||||
form.querySelector('input').focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,9 @@ class TagsHeaderView {
|
||||||
_evtSubmit(e) {
|
_evtSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._queryInputNode.blur();
|
this._queryInputNode.blur();
|
||||||
router.show(
|
this.dispatchEvent(new CustomEvent('navigate', {detail: {parameters: {
|
||||||
'/tags/' + misc.formatUrlParameters({
|
query: this._queryInputNode.value,
|
||||||
query: this._queryInputNode.value,
|
}}}));
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const router = require('../router.js');
|
const events = require('../events.js');
|
||||||
const keyboard = require('../util/keyboard.js');
|
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
|
const search = require('../util/search.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
|
|
||||||
const template = views.getTemplate('users-header');
|
const template = views.getTemplate('users-header');
|
||||||
|
|
||||||
class UsersHeaderView {
|
class UsersHeaderView extends events.EventTarget {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
|
super();
|
||||||
|
|
||||||
this._hostNode = ctx.hostNode;
|
this._hostNode = ctx.hostNode;
|
||||||
views.replaceContent(this._hostNode, template(ctx));
|
views.replaceContent(this._hostNode, template(ctx));
|
||||||
|
|
||||||
keyboard.bind('q', () => {
|
search.searchInputNodeFocusHelper(this._queryInputNode);
|
||||||
this._formNode.querySelector('input').focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,9 @@ class UsersHeaderView {
|
||||||
|
|
||||||
_evtSubmit(e) {
|
_evtSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._queryInputNode.blur();
|
this.dispatchEvent(new CustomEvent('navigate', {detail: {parameters: {
|
||||||
router.show(
|
query: this._queryInputNode.value,
|
||||||
'/users/' + misc.formatUrlParameters({
|
}}}));
|
||||||
query: this._queryInputNode.value,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue