diff --git a/app/controllers/admin/post.js b/app/controllers/admin/post.js index 771bb53..49f15d0 100644 --- a/app/controllers/admin/post.js +++ b/app/controllers/admin/post.js @@ -5,7 +5,9 @@ 'use strict'; const DTP_COMPONENT_NAME = 'admin:post'; + const express = require('express'); +const multer = require('multer'); const { SiteController, SiteError } = require('../../../lib/site-lib'); @@ -16,6 +18,8 @@ class PostController extends SiteController { } async start ( ) { + const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/uploads/${DTP_COMPONENT_NAME}` }); + const router = express.Router(); router.use(async (req, res, next) => { res.locals.currentView = 'admin'; @@ -25,6 +29,7 @@ class PostController extends SiteController { router.param('postId', this.populatePostId.bind(this)); + router.post('/:postId/image', upload.single('imageFile'), this.postUpdateImage.bind(this)); router.post('/:postId', this.postUpdatePost.bind(this)); router.post('/', this.postCreatePost.bind(this)); @@ -52,6 +57,29 @@ class PostController extends SiteController { } } + async postUpdateImage (req, res, next) { + const { post: postService } = this.dtp.services; + try { + const displayList = this.createDisplayList('post-image'); + + await postService.updateImage(req.user, res.locals.post, req.file); + + displayList.showNotification( + 'Profile photo updated successfully.', + 'success', + 'bottom-center', + 2000, + ); + res.status(200).json({ success: true, displayList }); + } catch (error) { + this.log.error('failed to update feature image', { error }); + return res.status(error.statusCode || 500).json({ + success: false, + message: error.message, + }); + } + } + async postUpdatePost (req, res, next) { const { post: postService } = this.dtp.services; try { diff --git a/app/services/post.js b/app/services/post.js index 8d9ef99..c4f14ce 100644 --- a/app/services/post.js +++ b/app/services/post.js @@ -24,6 +24,9 @@ class PostService extends SiteService { path: 'author', select: '_id username username_lc displayName bio picture', }, + { + path: 'image', + }, ]; } @@ -89,6 +92,29 @@ class PostService extends SiteService { ); } + async updateImage (user, post, file) { + const { image: imageService } = this.dtp.services; + const images = [ + { + width: 960, + height: 540, + format: 'jpeg', + formatParameters: { + quality: 80, + }, + }, + ]; + await imageService.processImageFile(user, file, images); + await Post.updateOne( + { _id: post._id }, + { + $set: { + image: images[0].image._id, + }, + }, + ); + } + async getPosts (pagination, status = ['published']) { if (!Array.isArray(status)) { status = [status]; diff --git a/app/views/admin/post/editor.pug b/app/views/admin/post/editor.pug index 50a413e..405c330 100644 --- a/app/views/admin/post/editor.pug +++ b/app/views/admin/post/editor.pug @@ -1,6 +1,12 @@ extends ../layouts/main +block vendorcss + link(rel='stylesheet', href=`/cropperjs/cropper.min.css?v=${pkg.version}`) +block vendorjs + script(src=`/cropperjs/cropper.min.js?v=${pkg.version}`) block content + include ../../components/file-upload-image + - var actionUrl = post ? `/admin/post/${post._id}` : `/admin/post`; form(method="POST", action= actionUrl).uk-form @@ -48,6 +54,19 @@ block content input(id="is-featured", name="isFeatured", type="checkbox", checked= post ? post.flags.isFeatured : false).uk-checkbox | Featured + if post + .uk-margin + label(for="post-image-file").uk-form-label Feature Image + +renderFileUploadImage( + `/admin/post/${post._id}/image`, + 'post-image-upload', + 'post-image-file', + 'responsive', + `/img/default-poster.jpg`, + post.image, + { aspectRatio: 16 / 9 }, + ) + block viewjs script(src="/tinymce/tinymce.min.js") script. diff --git a/app/views/components/file-upload-image.pug b/app/views/components/file-upload-image.pug index 54d9e87..78498c2 100644 --- a/app/views/components/file-upload-image.pug +++ b/app/views/components/file-upload-image.pug @@ -10,7 +10,7 @@ mixin renderFileUploadImage (actionUrl, containerId, imageId, imageClass, defaul if !!currentImage img(id= imageId, src= `/image/${currentImage._id}`, class= imageClass).sb-large else - img(id= imageId, src= defaultImage, class= imageClass).sb-large + img(id= imageId, src= defaultImage, class= imageClass) div(class="uk-width-1-1 uk-width-auto@m") .uk-text-small.uk-margin(hidden= !!currentImage) diff --git a/app/views/post/components/featured-item.pug b/app/views/post/components/featured-item.pug index 7171da7..73db7e2 100644 --- a/app/views/post/components/featured-item.pug +++ b/app/views/post/components/featured-item.pug @@ -1,7 +1,10 @@ mixin renderBlogPostFeaturedItem (post) a(href=`/post/${post.slug}`).uk-display-block.uk-link-reset div(class='uk-visible@m').uk-margin-small - img(src="/img/default-poster.jpg").responsive + if post.image + img(src= `/image/${post.image._id}`).responsive + else + img(src="/img/default-poster.jpg").responsive article.uk-article h4(style="line-height: 1.1;").uk-article-title.uk-margin-small= post.title diff --git a/app/views/post/components/list-item.pug b/app/views/post/components/list-item.pug index 15f9884..02bbce8 100644 --- a/app/views/post/components/list-item.pug +++ b/app/views/post/components/list-item.pug @@ -6,7 +6,10 @@ mixin renderBlogPostListItem (post, postIndex = 1, postIndexModulus = 3) 'uk-flex-first': ((postIndex % postIndexModulus) === 0), 'uk-flex-last': ((postIndex % postIndexModulus) !== 0), }).uk-width-1-3 - img(src="/img/default-poster.jpg").responsive + if post.image + img(src= `/image/${post.image._id}`).responsive + else + img(src="/img/default-poster.jpg").responsive div(class='uk-width-1-1 uk-width-2-3@m', class={ 'uk-flex-first': ((postIndex % postIndexModulus) !== 0),