From 56966812883f2d6bfc2445e6a8b9b85dd378d0b8 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 19 Mar 2023 22:41:29 -0500 Subject: [PATCH] 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 --- app/controllers/admin/page.js | 4 +- app/controllers/admin/post.js | 4 +- app/controllers/author.js | 46 +++--- app/controllers/post.js | 15 +- app/services/page.js | 166 ++++++++++++++++---- app/services/post.js | 9 +- app/views/admin/page/editor.pug | 9 +- app/views/admin/post/index.pug | 1 + app/views/author/components/draft-list.pug | 2 +- app/views/components/navbar.pug | 8 + app/views/components/off-canvas.pug | 5 + app/views/layouts/main.pug | 1 + app/views/post/author/all.pug | 4 +- app/views/post/author/components/credit.pug | 2 +- app/views/post/author/view.pug | 7 +- app/views/post/tag/view.pug | 2 +- app/views/post/view.pug | 6 +- yarn.lock | 6 +- 18 files changed, 221 insertions(+), 76 deletions(-) diff --git a/app/controllers/admin/page.js b/app/controllers/admin/page.js index 7418a04..2e0f2fb 100644 --- a/app/controllers/admin/page.js +++ b/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 }); diff --git a/app/controllers/admin/post.js b/app/controllers/admin/post.js index d412d95..0951aaa 100644 --- a/app/controllers/admin/post.js +++ b/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 }); diff --git a/app/controllers/author.js b/app/controllers/author.js index f3f6983..4c7b732 100644 --- a/app/controllers/author.js +++ b/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'); diff --git a/app/controllers/post.js b/app/controllers/post.js index 9d1b587..5b42399 100644 --- a/app/controllers/post.js +++ b/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) { diff --git a/app/services/page.js b/app/services/page.js index c460b3e..1bbc17f 100644 --- a/app/services/page.js +++ b/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', diff --git a/app/services/post.js b/app/services/post.js index 538851b..266eb6c 100644 --- a/app/services/post.js +++ b/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) { diff --git a/app/views/admin/page/editor.pug b/app/views/admin/page/editor.pug index 0ec8768..35864bc 100644 --- a/app/views/admin/page/editor.pug +++ b/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") diff --git a/app/views/admin/post/index.pug b/app/views/admin/post/index.pug index 33b5ff5..08d02e0 100644 --- a/app/views/admin/post/index.pug +++ b/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. \ No newline at end of file diff --git a/app/views/author/components/draft-list.pug b/app/views/author/components/draft-list.pug index 37d3ecc..1631c3c 100644 --- a/app/views/author/components/draft-list.pug +++ b/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 diff --git a/app/views/components/navbar.pug b/app/views/components/navbar.pug index 1cc2f51..50ea3bf 100644 --- a/app/views/components/navbar.pug +++ b/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 diff --git a/app/views/components/off-canvas.pug b/app/views/components/off-canvas.pug index 34e67ee..5425b91 100644 --- a/app/views/components/off-canvas.pug +++ b/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 diff --git a/app/views/layouts/main.pug b/app/views/layouts/main.pug index 3c5879e..e0164a3 100644 --- a/app/views/layouts/main.pug +++ b/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') diff --git a/app/views/post/author/all.pug b/app/views/post/author/all.pug index 15be7d8..e4b8176 100644 --- a/app/views/post/author/all.pug +++ b/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 ) \ No newline at end of file + .uk-card-footer + +renderPaginationBar(`/post/authors`, totalAuthorCount ) \ No newline at end of file diff --git a/app/views/post/author/components/credit.pug b/app/views/post/author/components/credit.pug index 120a508..2f64218 100644 --- a/app/views/post/author/components/credit.pug +++ b/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 diff --git a/app/views/post/author/view.pug b/app/views/post/author/view.pug index 76e9b8e..6a4791c 100644 --- a/app/views/post/author/view.pug +++ b/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 ) diff --git a/app/views/post/tag/view.pug b/app/views/post/tag/view.pug index b2aefb6..5b90cd2 100644 --- a/app/views/post/tag/view.pug +++ b/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 diff --git a/app/views/post/view.pug b/app/views/post/view.pug index 57d3d40..9be7337 100644 --- a/app/views/post/view.pug +++ b/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 diff --git a/yarn.lock b/yarn.lock index e2c8439..9a56025 100644 --- a/yarn.lock +++ b/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"