Browse Source

Changes to menu rendering and small touch-ups

- Make pages drop-down and display children when applicable
- Make pagination work in mutiple views
  - Author route author/posts
  - Author route author/drafts
  - Others such as author page views
- Added pagination to admin/post
pull/40/head
Andrew 1 year ago
parent
commit
5696681288
  1. 4
      app/controllers/admin/page.js
  2. 4
      app/controllers/admin/post.js
  3. 46
      app/controllers/author.js
  4. 15
      app/controllers/post.js
  5. 166
      app/services/page.js
  6. 9
      app/services/post.js
  7. 9
      app/views/admin/page/editor.pug
  8. 1
      app/views/admin/post/index.pug
  9. 2
      app/views/author/components/draft-list.pug
  10. 8
      app/views/components/navbar.pug
  11. 5
      app/views/components/off-canvas.pug
  12. 1
      app/views/layouts/main.pug
  13. 4
      app/views/post/author/all.pug
  14. 2
      app/views/post/author/components/credit.pug
  15. 7
      app/views/post/author/view.pug
  16. 2
      app/views/post/tag/view.pug
  17. 6
      app/views/post/view.pug
  18. 6
      yarn.lock

4
app/controllers/admin/page.js

@ -40,10 +40,12 @@ class PageController extends SiteController {
async populatePageId (req, res, next, pageId) {
const { page: pageService } = this.dtp.services;
try {
res.locals.page = await pageService.getById(pageId);
res.locals.page = await pageService.getById(pageId, true);
if (!res.locals.page) {
throw new SiteError(404, 'Page not found');
}
res.locals.isParentPage = await pageService.isParentPage(pageId);
return next();
} catch (error) {
this.log.error('failed to populate pageId', { pageId, error });

4
app/controllers/admin/post.js

@ -97,7 +97,9 @@ class PostController extends SiteController {
try {
res.locals.pageTitle = `Posts for ${this.dtp.config.site.name}`;
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.posts = await postService.getAllPosts(res.locals.pagination);
const {posts, totalPostCount} = await postService.getAllPosts(res.locals.pagination);
res.locals.posts = posts;
res.locals.totalPostCount = totalPostCount;
res.render('admin/post/index');
} catch (error) {
this.log.error('failed to fetch posts', { error });

46
app/controllers/author.js

@ -26,15 +26,11 @@ class AuthorController extends SiteController {
return res.redirect(302, '/welcome');
}
if (req.user.flags.isAdmin) {
return next();
}
const canAuthor = req.user.permissions.canAuthorPosts;
const canPublish = req.user.permissions.canPublishPosts;
if (!canAuthor && !canPublish) {
return next(new SiteError(403, 'Author privileges are required'));
return next(new SiteError(403, 'Author or Publisher privileges are required'));
}
return next();
}
@ -69,29 +65,35 @@ class AuthorController extends SiteController {
async getPublishedPostHome (req, res, next) {
const { post: postService } = this.dtp.services;
try {
const isAdmin = req.user.flags.isAdmin;
const canAuthor = req.user.permissions.canAuthorPosts;
const canPublish = req.user.permissions.canPublishPosts;
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.published = { };
if(canAuthor) {
if ( canPublish ) {
res.locals.published = await postService.getPosts( { skip: 0, cpp: 5 }, ['published'], true );
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, ['published'], true );
res.locals.published.posts = posts;
res.locals.published.posts.totalPostCount = totalPostCount;
res.locals.allPosts = true;
res.locals.published.all = true;
res.locals.published.posts.all = true;
} else {
res.locals.published = await postService.getForAuthor( req.user, ['published'], { skip: 0, cpp: 5 } );
res.locals.published = await postService.getForAuthor( req.user, ['published'], res.locals.pagination );
}
}
else if ( canPublish || isAdmin ) {
res.locals.published = await postService.getPosts( { skip: 0, cpp: 5 }, ['published'], true );
else if ( canPublish) {
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, ['published'], true );
res.locals.published.posts = posts;
res.locals.published.posts.totalPostCount = totalPostCount;
res.locals.published.all = true;
}
@ -105,31 +107,37 @@ class AuthorController extends SiteController {
async getDraftsHome (req, res, next) {
const { post: postService } = this.dtp.services;
try {
const isAdmin = req.user.flags.isAdmin;
const canAuthor = req.user.permissions.canAuthorPosts;
const canPublish = req.user.permissions.canPublishPosts;
res.locals.pagination = this.getPaginationParameters(req, 20);
const status = ['draft'];
res.locals.drafts = { };
if(canAuthor) {
if ( canPublish ) {
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, status, true );
res.locals.drafts = await postService.getPosts( { skip: 0, cpp: 5 }, status, true );
res.locals.drafts.posts = posts;
res.locals.drafts.posts.totalPostCount = totalPostCount;
res.locals.allPosts = true;
res.locals.drafts.all = true;
res.locals.drafts.posts.all = true;
} else {
res.locals.drafts = await postService.getForAuthor( req.user, status, { skip: 0, cpp: 5 } );
res.locals.drafts = await postService.getForAuthor( req.user, status, res.locals.pagination );
}
}
else if ( canPublish || isAdmin ) {
res.locals.drafts = await postService.getPosts( { skip: 0, cpp: 5 }, status, true );
res.locals.drafts.all = true;
else if ( canPublish) {
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, status, true );
res.locals.drafts.posts = posts;
res.locals.drafts.posts.totalPostCount = totalPostCount;
res.locals.allPosts = true;
res.locals.drafts.posts.all = true;
}
res.render('author/draft/index');

15
app/controllers/post.js

@ -414,9 +414,9 @@ class PostController extends SiteController {
try {
res.locals.pagination = this.getPaginationParameters(req, 20);
const {authors , totalAuthorCount } = await userService.getAuthors(res.locals.pagination);
res.locals.authors = authors;
res.locals.totalAuthorCount = totalAuthorCount;
const {authors , totalAuthorCount } = await userService.getAuthors(res.locals.pagination);
res.locals.authors = authors;
res.locals.totalAuthorCount = totalAuthorCount;
res.render('post/author/all');
} catch (error) {
return next(error);
@ -461,10 +461,11 @@ class PostController extends SiteController {
}
res.locals.allPosts = allPosts;
res.locals.tagSlug = tagSlug;
tagSlug = tagSlug.replace("_", " ");
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.posts = await postService.getByTags(tagSlug, res.locals.pagination, statusArray);
res.locals.tag = tagSlug;
res.locals.tag = tagSlug.replace("_", " ");
res.locals.pagination = this.getPaginationParameters(req, 12);
const {posts, totalPosts} = await postService.getByTags(res.locals.tag, res.locals.pagination, statusArray);
res.locals.posts = posts;
res.locals.totalPosts = totalPosts;
return next();
} catch (error) {

166
app/services/page.js

@ -7,7 +7,7 @@
const striptags = require('striptags');
const slug = require('slug');
const { SiteService, SiteError } = require('../../lib/site-lib');
const { SiteService, SiteError, SiteAsync } = require('../../lib/site-lib');
const mongoose = require('mongoose');
const ObjectId = mongoose.Types.ObjectId;
@ -22,26 +22,14 @@ class PageService extends SiteService {
async menuMiddleware (req, res, next) {
try {
const pages = await Page
.find({ status: 'published', parent: { $exists: false } })
.select('slug menu')
.lean();
res.locals.mainMenu = pages
.filter((page) => !page.parent)
.map((page) => {
return {
url: `/page/${page.slug}`,
slug: page.slug,
icon: page.menu.icon,
label: page.menu.label,
order: page.menu.order,
};
})
.sort((a, b) => {
return a.order < b.order;
});
let mainMenu = await this.getMainMenuPages();
if (!mainMenu) {
await this.cacheMainMenuPages();
mainMenu = await this.getMainMenuPages();
}
res.locals.mainMenu = mainMenu;
return next();
} catch (error) {
this.log.error('failed to build page menu', { error });
@ -71,6 +59,8 @@ class PageService extends SiteService {
}
await page.save();
await this.cacheMainMenuPages();
return page.toObject();
}
@ -121,14 +111,16 @@ class PageService extends SiteService {
if (!user.permissions.canPublishPages) {
throw new SiteError(403, 'You are not permitted to publish pages');
}
updateOp.$set.status = striptags(pageDefinition.status.trim());
}
updateOp.$set.status = striptags(pageDefinition.status.trim());
await Page.updateOne(
{ _id: page._id },
updateOp,
{ upsert: true },
);
await this.cacheMainMenuPages();
}
async getPages (pagination, status = ['published']) {
@ -144,12 +136,21 @@ class PageService extends SiteService {
return pages;
}
async getById (pageId) {
const page = await Page
async getById (pageId, populateParent) {
if (populateParent) {
const page = await Page
.findById(pageId)
.select('+content +menu')
.populate('menu.parent')
.lean();
return page;
} else {
const page = await Page
.findById(pageId)
.select('+content')
.lean();
return page;
return page;
}
}
async getBySlug (pageSlug) {
@ -158,12 +159,27 @@ class PageService extends SiteService {
return this.getById(pageId);
}
async isParentPage (page) {
if (page) {
page = [page];
}
const parentPage = await Page.distinct( 'menu.parent', { "menu.parent" : { $in : page } } );
return parentPage.length > 0;
}
async getAvailablePages (excludedPageIds) {
const search = { };
let search = { };
if (excludedPageIds) {
search._id = { $nin: excludedPageIds };
}
const pages = await Page.find(search).lean();
const pages = (await Page.find(search).lean()).filter((page) => {
if (page.menu && !page.menu.parent ) {
return page;
}
});
return pages;
}
@ -179,7 +195,101 @@ class PageService extends SiteService {
const pageSlug = slug(pageTitle.trim().toLowerCase()).split('-').slice(0, 4).join('-');
return `${pageSlug}-${pageId}`;
}
async cacheMainMenuPages () {
try {
const pages = await Page
.find({ status: 'published' })
.select('slug menu')
.populate({path: 'menu.parent'})
.lean();
let mainMenu = [];
await SiteAsync.each(pages, async (page) => {
if (page.menu.parent) {
let parent = page.menu.parent;
if (parent.status === 'published') {
let parentPage = mainMenu.find(item => item.slug === parent.slug);
if (parentPage) {
let childPage = {
url: `/page/${page.slug}`,
slug: page.slug,
icon: page.menu.icon,
label: page.menu.label,
order: page.menu.order,
};
parentPage.children.splice(childPage.order, 0, childPage);
}
else {
let parentPage = {
url: `/page/${parent.slug}`,
slug: parent.slug,
icon: parent.menu.icon,
label: parent.menu.label,
order: parent.menu.order,
children: [],
};
let childPage = {
url: `/page/${page.slug}`,
slug: page.slug,
icon: page.menu.icon,
label: page.menu.label,
order: page.menu.order,
};
parentPage.children.splice(childPage.order, 0, childPage);
mainMenu.splice(parentPage.order, 0, parentPage);
}
} else {
let menuPage = {
url: `/page/${page.slug}`,
slug: page.slug,
icon: page.menu.icon,
label: page.menu.label,
order: page.menu.order,
children: [],
};
mainMenu.splice(menuPage.order, 0, menuPage);
}
} else {
let isPageInMenu = mainMenu.find(item => item.slug === page.slug);
if (!isPageInMenu) {
let menuPage = {
url: `/page/${page.slug}`,
slug: page.slug,
icon: page.menu.icon,
label: page.menu.label,
order: page.menu.order,
children: [],
};
mainMenu.push(menuPage);
}
}
});
mainMenu.sort((a, b) => a.order - b.order);
await SiteAsync.each(mainMenu, async (menu) => {
if (menu.children) {
menu.children.sort((a, b) => a.order - b.order);
}
});
const deleteResponse = await this.dtp.services.cache.del("mainMenu");
this.dtp.log.info(deleteResponse);
const storeResponse = await this.dtp.services.cache.setObject("mainMenu", mainMenu);
this.dtp.log.info(storeResponse);
// const getresp = await this.dtp.services.cache.getObject("mainMenu");
} catch (error) {
this.dtp.log.error('failed to build page menu', { error });
}
}
async getMainMenuPages() {
return this.dtp.services.cache.getObject("mainMenu");
}
}
module.exports = {
slug: 'page',

9
app/services/post.js

@ -213,12 +213,14 @@ class PostService extends SiteService {
if (!Array.isArray(status)) {
status = [status];
}
const posts = await Post.find( { status: { $in: status }, tags: tag } )
// const search = { status: { $in: status }, tags: tag };
const posts = await Post.find( { status: { $in: status }, tags: tag })
.sort({ created: -1 })
.skip(pagination.skip)
.limit(pagination.cpp)
.populate(this.populatePost);
return posts;
const totalPosts = await Post.countDocuments({ status: { $in: status }, tags: tag });
return {posts, totalPosts};
}
async updateImage (user, post, file) {
@ -298,7 +300,8 @@ class PostService extends SiteService {
.limit(pagination.cpp)
.populate(this.populatePost)
.lean();
return posts;
const totalPostCount = await Post.countDocuments();
return {posts, totalPostCount};
}
async getForAuthor (author, status, pagination) {

9
app/views/admin/page/editor.pug

@ -47,13 +47,18 @@ block content
.uk-margin
label(for="menu-order").uk-form-label Menu item order
input(id="menu-order", name="menuOrder", type="number", min="0", value= page ? page.menu.order : 0).uk-input
if Array.isArray(availablePages) && (availablePages.length > 0)
if (Array.isArray(availablePages) && (availablePages.length > 0)) && !isParentPage
.uk-margin
label(for="menu-parent").uk-form-label Parent page
select(id="menu-parent", name="parentPageId").uk-select
if page && page.menu.parent
option(value= page.menu.parent._id)= page.menu.parent.title
option(value= "none") --- Select parent page ---
each menuPage in availablePages
option(value= menuPage._id)= menuPage.title
if page && page.menu.parent && !page.menu.parent._id.equals(menuPage._id)
option(value= menuPage._id)= menuPage.title
else if !page || !page.menu.parent
option(value= menuPage._id)= menuPage.title
block viewjs
script(src="/tinymce/tinymce.min.js")

1
app/views/admin/post/index.pug

@ -54,5 +54,6 @@ block content
onclick="return dtp.adminApp.deletePost(event);",
).uk-button.dtp-button-danger
+renderButtonIcon('fa-trash', 'Delete')
+renderPaginationBar('/admin/post', totalPostCount)
else
div There are no posts at this time.

2
app/views/author/components/draft-list.pug

@ -43,7 +43,7 @@ mixin renderFullDraftList (posts)
div(uk-grid).uk-grid-medium.uk-text-medium
.uk-width-expand
a(href=`/post/${draft.slug}`, title="Edit draft")= moment(draft.created).fromNow()
if drafts.all
if posts.all
span by
a(href=`/user/${draft.author.username}`)=` ${draft.author.username}`
.uk-width-auto

8
app/views/components/navbar.pug

@ -36,6 +36,14 @@ nav(uk-navbar).uk-navbar-container.uk-position-fixed.uk-position-top
li(class={ 'uk-active': (pageSlug === menuItem.slug) })
a(href= menuItem.url, title= menuItem.label)
+renderButtonIcon(menuItem.icon || 'fa-file', menuItem.label)
if Array.isArray(menuItem.children) && (menuItem.children.length > 0)
.uk-navbar-dropdown
ul.uk-nav.uk-navbar-dropdown-nav
each child in menuItem.children
li(class={ 'uk-active': (pageSlug === child.slug) })
a(href= child.url, title= child.label)
+renderButtonIcon(child.icon || 'fa-file', child.label)
if Array.isArray(links) && (links.length > 0)
li

5
app/views/components/off-canvas.pug

@ -47,6 +47,11 @@ mixin renderMenuItem (iconClass, label)
li(class={ 'uk-active': (pageSlug === menuItem.slug) })
a(href= menuItem.url, title= menuItem.label)
+renderMenuItem(menuItem.icon || 'fa-file', menuItem.label)
if Array.isArray(menuItem.children) && (menuItem.children.length > 0)
each child in menuItem.children
li(class={ 'uk-active': (pageSlug === child.slug) })
a(href= child.url, title= child.label)
+renderButtonIcon(child.icon || 'fa-file', child.label)
if user
li.uk-nav-header Member Menu

1
app/views/layouts/main.pug

@ -1,5 +1,6 @@
include ../components/library
include ../components/page-sidebar
include ../components/pagination-bar
include ../user/components/profile-icon
doctype html
html(lang='en')

4
app/views/post/author/all.pug

@ -12,5 +12,5 @@ block content
each author in authors
li
+renderAuthorCredit(author)
.uk-card-footer
+renderPaginationBar(`/post/authors`, totalAuthorCount )
.uk-card-footer
+renderPaginationBar(`/post/authors`, totalAuthorCount )

2
app/views/post/author/components/credit.pug

@ -13,7 +13,7 @@ mixin renderAuthorCredit (author)
.uk-text-small.uk-text-muted(style="line-height: 1em;")
if author.coreUserId
a(href=`${process.env.DTP_CORE_AUTH_SCHEME}://${author.core.meta.domain}/user/${author.coreUserId}`)= author.core.meta.name
else if !Array.isArray(posts)
if !Array.isArray(posts)
a(href= `/post/author/${author.username}`)= `View posts by author`
.uk-text-small= author.bio

7
app/views/post/author/view.pug

@ -29,7 +29,6 @@ block content
if Array.isArray(posts) && (posts.length > 0)
ul.uk-list.uk-list-divider
each post in posts
li
+renderPostSummaryFull(post)
.uk-card-footer
+renderPaginationBar(`/post/author/${author.username}`, posts.totalPostCount )
+renderPostSummaryFull(post)
.uk-card-footer
+renderPaginationBar(`/post/author/${author.username}`, totalPostCount )

2
app/views/post/tag/view.pug

@ -15,7 +15,7 @@ block content
+renderPostSummaryFull(post)
.uk-card-footer
+renderPaginationBar(`/post/tag/${tagSlug}`, posts.total )
+renderPaginationBar(`/post/tag/${tagSlug}`, totalPosts )
//- li
if post.image
img(src= `/image/${post.image._id}`, href=`/post/${post.slug}`, style="max-height: 350px; object-fit: cover; vertical-align:middle;margin:0px 20px;").responsive

6
app/views/post/view.pug

@ -24,13 +24,13 @@ block content
div(uk-grid)
.uk-width-auto
+renderGabShareButton(`https://${site.domainKey}/post/${post.slug}`, `${post.title} - ${post.summary}`)
+renderSectionTitle('Post tags')
if Array.isArray(post.tags) && (post.tags.length > 0)
+renderSectionTitle('Post tags')
div(uk-grid).uk-grid-small
each tag in post.tags
-
var tagSlug;
tagSlug = tag.replace(" ", "_")
var tagSlug;
tagSlug = tag.replace(" ", "_")
a(href=`/post/tag/${tagSlug}`).uk-display-block.uk-link-reset.uk-margin-small= tag
.uk-margin

6
yarn.lock

@ -2207,9 +2207,9 @@ camelcase@^6.2.0:
integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==
caniuse-lite@^1.0.30001280:
version "1.0.30001373"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz"
integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ==
version "1.0.30001467"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001467.tgz"
integrity sha512-cEdN/5e+RPikvl9AHm4uuLXxeCNq8rFsQ+lPHTfe/OtypP3WwnVVbjn+6uBV7PaFL6xUFzTh+sSCOz1rKhcO+Q==
caseless@~0.12.0:
version "0.12.0"

Loading…
Cancel
Save