feat: page published state + comments localization

This commit is contained in:
NGPixel 2020-06-20 22:08:59 -04:00
parent 83b83a7510
commit 4855051d87
7 changed files with 105 additions and 70 deletions

View File

@ -3,7 +3,7 @@
v-textarea#discussion-new( v-textarea#discussion-new(
outlined outlined
flat flat
placeholder='Write a new comment...' :placeholder='$t(`common:comments.newPlaceholder`)'
auto-grow auto-grow
dense dense
rows='3' rows='3'
@ -19,7 +19,7 @@
outlined outlined
color='blue-grey darken-2' color='blue-grey darken-2'
:background-color='$vuetify.theme.dark ? `grey darken-5` : `white`' :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
placeholder='Your Name' :placeholder='$t(`common:comments.fieldName`)'
hide-details hide-details
dense dense
autocomplete='name' autocomplete='name'
@ -30,7 +30,7 @@
outlined outlined
color='blue-grey darken-2' color='blue-grey darken-2'
:background-color='$vuetify.theme.dark ? `grey darken-5` : `white`' :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
placeholder='Your Email Address' :placeholder='$t(`common:comments.fieldEmail`)'
hide-details hide-details
type='email' type='email'
dense dense
@ -39,9 +39,11 @@
) )
.d-flex.align-center.pt-3(v-if='permissions.write') .d-flex.align-center.pt-3(v-if='permissions.write')
v-icon.mr-1(color='blue-grey') mdi-language-markdown-outline 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 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( v-btn(
dark dark
color='blue-grey darken-2' color='blue-grey darken-2'
@ -49,7 +51,7 @@
depressed depressed
) )
v-icon(left) mdi-comment 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') v-divider.mt-3(v-if='permissions.write')
.pa-5.d-flex.align-center.justify-center(v-if='isLoading && !hasLoadedOnce') .pa-5.d-flex.align-center.justify-center(v-if='isLoading && !hasLoadedOnce')
v-progress-circular( v-progress-circular(
@ -58,7 +60,7 @@
width='1' width='1'
color='blue-grey' 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( v-timeline(
dense dense
v-else-if='comments && comments.length > 0' v-else-if='comments && comments.length > 0'
@ -80,7 +82,7 @@
v-icon.mr-3(small, @click='editComment(cm)') mdi-pencil v-icon.mr-3(small, @click='editComment(cm)') mdi-pencil
v-icon(small, @click='deleteCommentConfirm(cm)') mdi-delete v-icon(small, @click='deleteCommentConfirm(cm)') mdi-delete
.comments-post-name.caption: strong {{cm.authorName}} .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-content.mt-3(v-if='commentEditId !== cm.id', v-html='cm.render')
.comments-post-editcontent.mt-3(v-else) .comments-post-editcontent.mt-3(v-else)
v-textarea( v-textarea(
@ -103,7 +105,7 @@
outlined outlined
) )
v-icon(left) mdi-close v-icon(left) mdi-close
span.text-none Cancel span.text-none {{$t('common:action.cancel')}}
v-btn( v-btn(
dark dark
color='blue-grey darken-2' color='blue-grey darken-2'
@ -111,16 +113,16 @@
depressed depressed
) )
v-icon(left) mdi-comment v-icon(left) mdi-comment
span.text-none Update Comment span.text-none {{$t('common:comments.updateComment')}}
.pt-5.text-center.body-2.blue-grey--text(v-else-if='permissions.write') Be the first to comment. .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) No comments yet. .text-center.body-2.blue-grey--text(v-else) {{$t('common:comments.none')}}
v-dialog(v-model='deleteCommentDialogShown', max-width='500') v-dialog(v-model='deleteCommentDialogShown', max-width='500')
v-card v-card
.dialog-header.is-red Confirm Delete .dialog-header.is-red {{$t('common:comments.deleteConfirmTitle')}}
v-card-text.pt-5 v-card-text.pt-5
span Are you sure you want to permanently delete this comment? span {{$t('common:comments.deleteWarn')}}
.caption: strong This action cannot be undone! .caption: strong {{$t('common:comments.deletePermanentWarn')}}
v-card-chin v-card-chin
v-spacer v-spacer
v-btn(text, @click='deleteCommentDialogShown = false') {{$t('common:actions.cancel')}} 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)) { if (_.get(resp, 'data.comments.create.responseResult.succeeded', false)) {
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
style: 'success', style: 'success',
message: 'New comment posted successfully.', message: this.$t('common:comments.postSuccess'),
icon: 'check' icon: 'check'
}) })
@ -371,7 +373,7 @@ export default {
this.isBusy = true this.isBusy = true
try { try {
if (this.commentEditContent.length < 2) { 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({ const resp = await this.$apollo.mutate({
mutation: gql` mutation: gql`
@ -404,7 +406,7 @@ export default {
if (_.get(resp, 'data.comments.update.responseResult.succeeded', false)) { if (_.get(resp, 'data.comments.update.responseResult.succeeded', false)) {
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
style: 'success', style: 'success',
message: 'Comment was updated successfully.', message: this.$t('common:comments.updateSuccess'),
icon: 'check' icon: 'check'
}) })
@ -470,7 +472,7 @@ export default {
if (_.get(resp, 'data.comments.delete.responseResult.succeeded', false)) { if (_.get(resp, 'data.comments.delete.responseResult.succeeded', false)) {
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
style: 'success', style: 'success',
message: 'Comment was deleted successfully.', message: this.$t('common:comments.deleteSuccess'),
icon: 'check' icon: 'check'
}) })

View File

@ -115,6 +115,14 @@ export default {
type: String, type: String,
default: '' default: ''
}, },
publishStartDate: {
type: String,
default: ''
},
publishEndDate: {
type: String,
default: ''
},
scriptJs: { scriptJs: {
type: String, type: String,
default: '' default: ''
@ -196,6 +204,8 @@ export default {
this.$store.set('page/id', this.pageId) this.$store.set('page/id', this.pageId)
this.$store.set('page/description', this.description) this.$store.set('page/description', this.description)
this.$store.set('page/isPublished', this.isPublished) 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/locale', this.locale)
this.$store.set('page/path', this.path) this.$store.set('page/path', this.path)
this.$store.set('page/tags', this.tags) this.$store.set('page/tags', this.tags)

View File

@ -136,12 +136,12 @@
) )
v-spacer v-spacer
v-btn( v-btn(
flat='' text
color='primary' color='primary'
@click='isPublishStartShown = false' @click='isPublishStartShown = false'
) {{$t('common:actions.cancel')}} ) {{$t('common:actions.cancel')}}
v-btn( v-btn(
flat='' text
color='primary' color='primary'
@click='$refs.menuPublishStart.save(publishStartDate)' @click='$refs.menuPublishStart.save(publishStartDate)'
) {{$t('common:actions.ok')}} ) {{$t('common:actions.ok')}}
@ -177,12 +177,12 @@
) )
v-spacer v-spacer
v-btn( v-btn(
flat='' text
color='primary' color='primary'
@click='isPublishEndShown = false' @click='isPublishEndShown = false'
) {{$t('common:actions.cancel')}} ) {{$t('common:actions.cancel')}}
v-btn( v-btn(
flat='' text
color='primary' color='primary'
@click='$refs.menuPublishEnd.save(publishEndDate)' @click='$refs.menuPublishEnd.save(publishEndDate)'
) {{$t('common:actions.ok')}} ) {{$t('common:actions.ok')}}

View File

@ -260,12 +260,14 @@
v-icon(size='20') mdi-trash-can-outline v-icon(size='20') mdi-trash-can-outline
span {{$t('common:header.delete')}} span {{$t('common:header.delete')}}
span {{$t('common:page.editPage')}} 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') .contents(ref='container')
slot(name='contents') slot(name='contents')
.comments-container#discussion(v-if='commentsEnabled && commentsPerms.read') .comments-container#discussion(v-if='commentsEnabled && commentsPerms.read')
.comments-header .comments-header
v-icon.mr-2(dark) mdi-comment-text-outline v-icon.mr-2(dark) mdi-comment-text-outline
span Comments span {{$t('common:comments.title')}}
.comments-main .comments-main
slot(name='comments') slot(name='comments')
nav-footer nav-footer
@ -503,7 +505,7 @@ export default {
this.$store.set('page/title', this.title) this.$store.set('page/title', this.title)
this.$store.set('page/updatedAt', this.updatedAt) this.$store.set('page/updatedAt', this.updatedAt)
if (this.effectivePermissions) { 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') this.$store.set('page/mode', 'view')

View File

@ -3,37 +3,12 @@ const router = express.Router()
const pageHelper = require('../helpers/page') const pageHelper = require('../helpers/page')
const _ = require('lodash') const _ = require('lodash')
const CleanCSS = require('clean-css') const CleanCSS = require('clean-css')
const moment = require('moment')
/* global WIKI */ /* global WIKI */
const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/ 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 * Robots.txt
*/ */
@ -137,6 +112,9 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
pageArgs.tags = _.get(page, 'tags', []) pageArgs.tags = _.get(page, 'tags', [])
// -> Effective Permissions
const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
const injectCode = { const injectCode = {
css: WIKI.config.theming.injectCSS, css: WIKI.config.theming.injectCSS,
head: WIKI.config.theming.injectHead, head: WIKI.config.theming.injectHead,
@ -145,7 +123,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
if (page) { if (page) {
// -> EDIT MODE // -> 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') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'edit' }) 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') page.content = Buffer.from(page.content).toString('base64')
} else { } else {
// -> CREATE MODE // -> CREATE MODE
if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) { if (!effectivePermissions.pages.write) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'create' }) 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 }) res.render('editor', { page, injectCode, effectivePermissions })
}) })
@ -262,7 +237,9 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
pageArgs.tags = _.get(page, 'tags', []) 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') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'history' }) 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.title', page.title)
_.set(res.locals, 'pageMeta.description', page.description) _.set(res.locals, 'pageMeta.description', page.description)
// -> Effective Permissions
const effectivePermissions = getPageEffectivePermissions(req, pageArgs)
res.render('history', { page, effectivePermissions }) res.render('history', { page, effectivePermissions })
} else { } else {
res.redirect(`/${pageArgs.path}`) res.redirect(`/${pageArgs.path}`)
@ -346,16 +320,19 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`) 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.lang', pageArgs.locale)
_.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
if (versionId > 0) { if (versionId > 0) {
if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) { if (!effectivePermissions.history.read) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'sourceVersion' }) return res.render('unauthorized', { action: 'sourceVersion' })
} }
} else { } else {
if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) { if (!effectivePermissions.source.read) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'source' }) 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.title', page.title)
_.set(res.locals, 'pageMeta.description', page.description) _.set(res.locals, 'pageMeta.description', page.description)
// -> Effective Permissions
const effectivePermissions = getPageEffectivePermissions(req, pageArgs)
res.render('source', { page, effectivePermissions }) res.render('source', { page, effectivePermissions })
} }
} else { } else {
@ -419,8 +393,11 @@ router.get('/*', async (req, res, next) => {
}) })
pageArgs.tags = _.get(page, 'tags', []) pageArgs.tags = _.get(page, 'tags', [])
// -> Effective Permissions
const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// -> Check User Access // -> Check User Access
if (!WIKI.auth.checkAccess(req.user, ['read:pages'], pageArgs)) { if (!effectivePermissions.pages.read) {
if (req.user.id === 2) { if (req.user.id === 2) {
res.cookie('loginRedirect', req.path, { res.cookie('loginRedirect', req.path, {
maxAge: 15 * 60 * 1000 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.title', page.title)
_.set(res.locals, 'pageMeta.description', page.description) _.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 // -> Build sidebar navigation
let sdi = 1 let sdi = 1
const sidebar = (await WIKI.models.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({ 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 // -> Render view
res.render('page', { res.render('page', {
page, page,
@ -516,7 +505,7 @@ router.get('/*', async (req, res, next) => {
res.render('welcome', { locale: pageArgs.locale }) res.render('welcome', { locale: pageArgs.locale })
} else { } else {
_.set(res.locals, 'pageMeta.title', 'Page Not Found') _.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 }) res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
} else { } else {
res.status(404).render('notfound', { action: 'view' }) res.status(404).render('notfound', { action: 'view' })

View File

@ -380,5 +380,35 @@ module.exports = {
WIKI.events.inbound.on('reloadAuthStrategies', () => { WIKI.events.inbound.on('reloadAuthStrategies', () => {
WIKI.auth.activateStrategies() 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)
}
}
} }
} }

View File

@ -14,6 +14,8 @@ block body
description=page.description description=page.description
:tags=page.tags :tags=page.tags
:is-published=page.isPublished :is-published=page.isPublished
publish-start-date=page.publishStartDate
publish-end-date=page.publishEndDate
script-css=page.extra.css script-css=page.extra.css
script-js=page.extra.js script-js=page.extra.js
init-mode=page.mode init-mode=page.mode