From 4855051d87dad6537aefd34fb1ea5b3b8e55ae74 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 20 Jun 2020 22:08:59 -0400 Subject: [PATCH] feat: page published state + comments localization --- client/components/comments.vue | 40 +++++----- client/components/editor.vue | 10 +++ .../editor/editor-modal-properties.vue | 8 +- client/themes/default/components/page.vue | 6 +- server/controllers/common.js | 79 ++++++++----------- server/core/auth.js | 30 +++++++ server/views/editor.pug | 2 + 7 files changed, 105 insertions(+), 70 deletions(-) diff --git a/client/components/comments.vue b/client/components/comments.vue index 3275d1e6..2cfa6f2b 100644 --- a/client/components/comments.vue +++ b/client/components/comments.vue @@ -3,7 +3,7 @@ v-textarea#discussion-new( outlined flat - placeholder='Write a new comment...' + :placeholder='$t(`common:comments.newPlaceholder`)' auto-grow dense rows='3' @@ -19,7 +19,7 @@ outlined color='blue-grey darken-2' :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`' - placeholder='Your Name' + :placeholder='$t(`common:comments.fieldName`)' hide-details dense autocomplete='name' @@ -30,7 +30,7 @@ outlined color='blue-grey darken-2' :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`' - placeholder='Your Email Address' + :placeholder='$t(`common:comments.fieldEmail`)' hide-details type='email' dense @@ -39,9 +39,11 @@ ) .d-flex.align-center.pt-3(v-if='permissions.write') v-icon.mr-1(color='blue-grey') mdi-language-markdown-outline - .caption.blue-grey--text Markdown Format + .caption.blue-grey--text {{$t('common:comments.markdownFormat')}} v-spacer - .caption.mr-3(v-if='isAuthenticated') Posting as #[strong {{userDisplayName}}] + .caption.mr-3(v-if='isAuthenticated') + i18next(tag='span', path='common:comments.postingAs') + strong(place='bold') {{userDisplayName}} v-btn( dark color='blue-grey darken-2' @@ -49,7 +51,7 @@ depressed ) v-icon(left) mdi-comment - span.text-none Post Comment + span.text-none {{$t('common:comments.postComment')}} v-divider.mt-3(v-if='permissions.write') .pa-5.d-flex.align-center.justify-center(v-if='isLoading && !hasLoadedOnce') v-progress-circular( @@ -58,7 +60,7 @@ width='1' color='blue-grey' ) - .caption.blue-grey--text.pl-3: em Loading comments... + .caption.blue-grey--text.pl-3: em {{$t('common:comments.loading')}} v-timeline( dense v-else-if='comments && comments.length > 0' @@ -80,7 +82,7 @@ v-icon.mr-3(small, @click='editComment(cm)') mdi-pencil v-icon(small, @click='deleteCommentConfirm(cm)') mdi-delete .comments-post-name.caption: strong {{cm.authorName}} - .comments-post-date.overline.grey--text {{cm.createdAt | moment('from') }} #[em(v-if='cm.createdAt !== cm.updatedAt') - modified {{cm.updatedAt | moment('from') }}] + .comments-post-date.overline.grey--text {{cm.createdAt | moment('from') }} #[em(v-if='cm.createdAt !== cm.updatedAt') - {{$t('common:comments.modified', { reldate: $options.filters.moment(cm.updatedAt, 'from') })}}] .comments-post-content.mt-3(v-if='commentEditId !== cm.id', v-html='cm.render') .comments-post-editcontent.mt-3(v-else) v-textarea( @@ -103,7 +105,7 @@ outlined ) v-icon(left) mdi-close - span.text-none Cancel + span.text-none {{$t('common:action.cancel')}} v-btn( dark color='blue-grey darken-2' @@ -111,16 +113,16 @@ depressed ) v-icon(left) mdi-comment - span.text-none Update Comment - .pt-5.text-center.body-2.blue-grey--text(v-else-if='permissions.write') Be the first to comment. - .text-center.body-2.blue-grey--text(v-else) No comments yet. + span.text-none {{$t('common:comments.updateComment')}} + .pt-5.text-center.body-2.blue-grey--text(v-else-if='permissions.write') {{$t('common:comments.beFirst')}} + .text-center.body-2.blue-grey--text(v-else) {{$t('common:comments.none')}} v-dialog(v-model='deleteCommentDialogShown', max-width='500') v-card - .dialog-header.is-red Confirm Delete + .dialog-header.is-red {{$t('common:comments.deleteConfirmTitle')}} v-card-text.pt-5 - span Are you sure you want to permanently delete this comment? - .caption: strong This action cannot be undone! + span {{$t('common:comments.deleteWarn')}} + .caption: strong {{$t('common:comments.deletePermanentWarn')}} v-card-chin v-spacer v-btn(text, @click='deleteCommentDialogShown = false') {{$t('common:actions.cancel')}} @@ -298,7 +300,7 @@ export default { if (_.get(resp, 'data.comments.create.responseResult.succeeded', false)) { this.$store.commit('showNotification', { style: 'success', - message: 'New comment posted successfully.', + message: this.$t('common:comments.postSuccess'), icon: 'check' }) @@ -371,7 +373,7 @@ export default { this.isBusy = true try { if (this.commentEditContent.length < 2) { - throw new Error('Comment is empty or too short!') + throw new Error(this.$t('common:comments.contentMissingError')) } const resp = await this.$apollo.mutate({ mutation: gql` @@ -404,7 +406,7 @@ export default { if (_.get(resp, 'data.comments.update.responseResult.succeeded', false)) { this.$store.commit('showNotification', { style: 'success', - message: 'Comment was updated successfully.', + message: this.$t('common:comments.updateSuccess'), icon: 'check' }) @@ -470,7 +472,7 @@ export default { if (_.get(resp, 'data.comments.delete.responseResult.succeeded', false)) { this.$store.commit('showNotification', { style: 'success', - message: 'Comment was deleted successfully.', + message: this.$t('common:comments.deleteSuccess'), icon: 'check' }) diff --git a/client/components/editor.vue b/client/components/editor.vue index 2c2b5c3b..c93b7263 100644 --- a/client/components/editor.vue +++ b/client/components/editor.vue @@ -115,6 +115,14 @@ export default { type: String, default: '' }, + publishStartDate: { + type: String, + default: '' + }, + publishEndDate: { + type: String, + default: '' + }, scriptJs: { type: String, default: '' @@ -196,6 +204,8 @@ export default { this.$store.set('page/id', this.pageId) this.$store.set('page/description', this.description) this.$store.set('page/isPublished', this.isPublished) + this.$store.set('page/publishStartDate', this.publishStartDate) + this.$store.set('page/publishEndDate', this.publishEndDate) this.$store.set('page/locale', this.locale) this.$store.set('page/path', this.path) this.$store.set('page/tags', this.tags) diff --git a/client/components/editor/editor-modal-properties.vue b/client/components/editor/editor-modal-properties.vue index cc185ba5..09cc8830 100644 --- a/client/components/editor/editor-modal-properties.vue +++ b/client/components/editor/editor-modal-properties.vue @@ -136,12 +136,12 @@ ) v-spacer v-btn( - flat='' + text color='primary' @click='isPublishStartShown = false' ) {{$t('common:actions.cancel')}} v-btn( - flat='' + text color='primary' @click='$refs.menuPublishStart.save(publishStartDate)' ) {{$t('common:actions.ok')}} @@ -177,12 +177,12 @@ ) v-spacer v-btn( - flat='' + text color='primary' @click='isPublishEndShown = false' ) {{$t('common:actions.cancel')}} v-btn( - flat='' + text color='primary' @click='$refs.menuPublishEnd.save(publishEndDate)' ) {{$t('common:actions.ok')}} diff --git a/client/themes/default/components/page.vue b/client/themes/default/components/page.vue index 351f692d..11a0dfe7 100644 --- a/client/themes/default/components/page.vue +++ b/client/themes/default/components/page.vue @@ -260,12 +260,14 @@ v-icon(size='20') mdi-trash-can-outline span {{$t('common:header.delete')}} span {{$t('common:page.editPage')}} + v-alert.mb-5(v-if='!isPublished', color='red', outlined, icon='mdi-minus-circle', dense) + .caption {{$t('common:page.unpublishedWarning')}} .contents(ref='container') slot(name='contents') .comments-container#discussion(v-if='commentsEnabled && commentsPerms.read') .comments-header v-icon.mr-2(dark) mdi-comment-text-outline - span Comments + span {{$t('common:comments.title')}} .comments-main slot(name='comments') nav-footer @@ -503,7 +505,7 @@ export default { this.$store.set('page/title', this.title) this.$store.set('page/updatedAt', this.updatedAt) if (this.effectivePermissions) { - this.$store.set('page/effectivePermissions',JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) + this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) } this.$store.set('page/mode', 'view') diff --git a/server/controllers/common.js b/server/controllers/common.js index a7d3ed77..16bcc820 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -3,37 +3,12 @@ const router = express.Router() const pageHelper = require('../helpers/page') const _ = require('lodash') const CleanCSS = require('clean-css') +const moment = require('moment') /* global WIKI */ const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/ -const getPageEffectivePermissions = (req, page) => { - return { - comments: { - read: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['read:comments'], page) : false, - write: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['write:comments'], page) : false, - manage: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['manage:comments'], page) : false - }, - history: { - read: WIKI.auth.checkAccess(req.user, ['read:history'], page) - }, - source: { - read: WIKI.auth.checkAccess(req.user, ['read:source'], page) - }, - pages: { - write: WIKI.auth.checkAccess(req.user, ['write:pages'], page), - manage: WIKI.auth.checkAccess(req.user, ['manage:pages'], page), - delete: WIKI.auth.checkAccess(req.user, ['delete:pages'], page), - script: WIKI.auth.checkAccess(req.user, ['write:scripts'], page), - style: WIKI.auth.checkAccess(req.user, ['write:styles'], page) - }, - system: { - manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page) - } - } -} - /** * Robots.txt */ @@ -137,6 +112,9 @@ router.get(['/e', '/e/*'], async (req, res, next) => { pageArgs.tags = _.get(page, 'tags', []) + // -> Effective Permissions + const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) + const injectCode = { css: WIKI.config.theming.injectCSS, head: WIKI.config.theming.injectHead, @@ -145,7 +123,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => { if (page) { // -> EDIT MODE - if (!WIKI.auth.checkAccess(req.user, ['write:pages', 'manage:pages'], pageArgs)) { + if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) { _.set(res.locals, 'pageMeta.title', 'Unauthorized') return res.render('unauthorized', { action: 'edit' }) } @@ -166,7 +144,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => { page.content = Buffer.from(page.content).toString('base64') } else { // -> CREATE MODE - if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) { + if (!effectivePermissions.pages.write) { _.set(res.locals, 'pageMeta.title', 'Unauthorized') return res.render('unauthorized', { action: 'create' }) } @@ -229,9 +207,6 @@ router.get(['/e', '/e/*'], async (req, res, next) => { } } - // -> Effective Permissions - const effectivePermissions = getPageEffectivePermissions(req, pageArgs) - res.render('editor', { page, injectCode, effectivePermissions }) }) @@ -262,7 +237,9 @@ router.get(['/h', '/h/*'], async (req, res, next) => { pageArgs.tags = _.get(page, 'tags', []) - if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) { + const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) + + if (!effectivePermissions.history.read) { _.set(res.locals, 'pageMeta.title', 'Unauthorized') return res.render('unauthorized', { action: 'history' }) } @@ -271,9 +248,6 @@ router.get(['/h', '/h/*'], async (req, res, next) => { _.set(res.locals, 'pageMeta.title', page.title) _.set(res.locals, 'pageMeta.description', page.description) - // -> Effective Permissions - const effectivePermissions = getPageEffectivePermissions(req, pageArgs) - res.render('history', { page, effectivePermissions }) } else { res.redirect(`/${pageArgs.path}`) @@ -346,16 +320,19 @@ router.get(['/s', '/s/*'], async (req, res, next) => { return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`) } + // -> Effective Permissions + const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) + _.set(res, 'locals.siteConfig.lang', pageArgs.locale) _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') if (versionId > 0) { - if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) { + if (!effectivePermissions.history.read) { _.set(res.locals, 'pageMeta.title', 'Unauthorized') return res.render('unauthorized', { action: 'sourceVersion' }) } } else { - if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) { + if (!effectivePermissions.source.read) { _.set(res.locals, 'pageMeta.title', 'Unauthorized') return res.render('unauthorized', { action: 'source' }) } @@ -376,9 +353,6 @@ router.get(['/s', '/s/*'], async (req, res, next) => { _.set(res.locals, 'pageMeta.title', page.title) _.set(res.locals, 'pageMeta.description', page.description) - // -> Effective Permissions - const effectivePermissions = getPageEffectivePermissions(req, pageArgs) - res.render('source', { page, effectivePermissions }) } } else { @@ -419,8 +393,11 @@ router.get('/*', async (req, res, next) => { }) pageArgs.tags = _.get(page, 'tags', []) + // -> Effective Permissions + const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs) + // -> Check User Access - if (!WIKI.auth.checkAccess(req.user, ['read:pages'], pageArgs)) { + if (!effectivePermissions.pages.read) { if (req.user.id === 2) { res.cookie('loginRedirect', req.path, { maxAge: 15 * 60 * 1000 @@ -442,6 +419,21 @@ router.get('/*', async (req, res, next) => { _.set(res.locals, 'pageMeta.title', page.title) _.set(res.locals, 'pageMeta.description', page.description) + // -> Check Publishing State + let pageIsPublished = page.isPublished + if (pageIsPublished && !_.isEmpty(page.publishStartDate)) { + pageIsPublished = moment(page.publishStartDate).isSameOrBefore() + } + if (pageIsPublished && !_.isEmpty(page.publishEndDate)) { + pageIsPublished = moment(page.publishEndDate).isSameOrAfter() + } + if (!pageIsPublished && !effectivePermissions.pages.write) { + _.set(res.locals, 'pageMeta.title', 'Unauthorized') + return res.status(403).render('unauthorized', { + action: 'view' + }) + } + // -> Build sidebar navigation let sdi = 1 const sidebar = (await WIKI.models.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({ @@ -499,9 +491,6 @@ router.get('/*', async (req, res, next) => { }) } - // -> Effective Permissions - const effectivePermissions = getPageEffectivePermissions(req, pageArgs) - // -> Render view res.render('page', { page, @@ -516,7 +505,7 @@ router.get('/*', async (req, res, next) => { res.render('welcome', { locale: pageArgs.locale }) } else { _.set(res.locals, 'pageMeta.title', 'Page Not Found') - if (WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) { + if (effectivePermissions.pages.write) { res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale }) } else { res.status(404).render('notfound', { action: 'view' }) diff --git a/server/core/auth.js b/server/core/auth.js index 06ce99c6..87d9fcce 100644 --- a/server/core/auth.js +++ b/server/core/auth.js @@ -380,5 +380,35 @@ module.exports = { WIKI.events.inbound.on('reloadAuthStrategies', () => { WIKI.auth.activateStrategies() }) + }, + + /** + * Get all user permissions for a specific page + */ + getEffectivePermissions (req, page) { + return { + comments: { + read: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['read:comments'], page) : false, + write: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['write:comments'], page) : false, + manage: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['manage:comments'], page) : false + }, + history: { + read: WIKI.auth.checkAccess(req.user, ['read:history'], page) + }, + source: { + read: WIKI.auth.checkAccess(req.user, ['read:source'], page) + }, + pages: { + read: WIKI.auth.checkAccess(req.user, ['read:pages'], page), + write: WIKI.auth.checkAccess(req.user, ['write:pages'], page), + manage: WIKI.auth.checkAccess(req.user, ['manage:pages'], page), + delete: WIKI.auth.checkAccess(req.user, ['delete:pages'], page), + script: WIKI.auth.checkAccess(req.user, ['write:scripts'], page), + style: WIKI.auth.checkAccess(req.user, ['write:styles'], page) + }, + system: { + manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page) + } + } } } diff --git a/server/views/editor.pug b/server/views/editor.pug index 30292b69..0f0b64c0 100644 --- a/server/views/editor.pug +++ b/server/views/editor.pug @@ -14,6 +14,8 @@ block body description=page.description :tags=page.tags :is-published=page.isPublished + publish-start-date=page.publishStartDate + publish-end-date=page.publishEndDate script-css=page.extra.css script-js=page.extra.js init-mode=page.mode