123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- <template lang="pug">
- div(v-intersect.once='onIntersect')
- v-textarea#discussion-new(
- outlined
- flat
- :placeholder='$t(`common:comments.newPlaceholder`)'
- auto-grow
- dense
- rows='3'
- hide-details
- v-model='newcomment'
- color='blue-grey darken-2'
- :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
- v-if='permissions.write'
- :aria-label='$t(`common:comments.fieldContent`)'
- )
- v-row.mt-2(dense, v-if='!isAuthenticated && permissions.write')
- v-col(cols='12', lg='6')
- v-text-field(
- outlined
- color='blue-grey darken-2'
- :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
- :placeholder='$t(`common:comments.fieldName`)'
- hide-details
- dense
- autocomplete='name'
- v-model='guestName'
- :aria-label='$t(`common:comments.fieldName`)'
- )
- v-col(cols='12', lg='6')
- v-text-field(
- outlined
- color='blue-grey darken-2'
- :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
- :placeholder='$t(`common:comments.fieldEmail`)'
- hide-details
- type='email'
- dense
- autocomplete='email'
- v-model='guestEmail'
- :aria-label='$t(`common:comments.fieldEmail`)'
- )
- .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 {{$t('common:comments.markdownFormat')}}
- v-spacer
- .caption.mr-3(v-if='isAuthenticated')
- i18next(tag='span', path='common:comments.postingAs')
- strong(place='name') {{userDisplayName}}
- v-btn(
- dark
- color='blue-grey darken-2'
- @click='postComment'
- depressed
- :aria-label='$t(`common:comments.postComment`)'
- )
- v-icon(left) mdi-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(
- indeterminate
- size='20'
- width='1'
- color='blue-grey'
- )
- .caption.blue-grey--text.pl-3: em {{$t('common:comments.loading')}}
- v-timeline(
- dense
- v-else-if='comments && comments.length > 0'
- )
- v-timeline-item.comments-post(
- color='pink darken-4'
- large
- v-for='cm of comments'
- :key='`comment-` + cm.id'
- :id='`comment-post-id-` + cm.id'
- )
- template(v-slot:icon)
- v-avatar(color='blue-grey')
- //- v-img(src='http://i.pravatar.cc/64')
- span.white--text.title {{cm.initials}}
- v-card.elevation-1
- v-card-text
- .comments-post-actions(v-if='permissions.manage && !isBusy && commentEditId === 0')
- 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') - {{$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(
- outlined
- flat
- auto-grow
- dense
- rows='3'
- hide-details
- v-model='commentEditContent'
- color='blue-grey darken-2'
- :background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
- )
- .d-flex.align-center.pt-3
- v-spacer
- v-btn.mr-3(
- dark
- color='blue-grey darken-2'
- @click='editCommentCancel'
- outlined
- )
- v-icon(left) mdi-close
- span.text-none {{$t('common:actions.cancel')}}
- v-btn(
- dark
- color='blue-grey darken-2'
- @click='updateComment'
- depressed
- )
- v-icon(left) mdi-comment
- 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 {{$t('common:comments.deleteConfirmTitle')}}
- v-card-text.pt-5
- 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')}}
- v-btn(color='red', dark, @click='deleteComment') {{$t('common:actions.delete')}}
- </template>
- <script>
- import gql from 'graphql-tag'
- import { get } from 'vuex-pathify'
- import validate from 'validate.js'
- import _ from 'lodash'
- export default {
- data () {
- return {
- newcomment: '',
- isLoading: true,
- hasLoadedOnce: false,
- comments: [],
- guestName: '',
- guestEmail: '',
- commentToDelete: {},
- commentEditId: 0,
- commentEditContent: null,
- deleteCommentDialogShown: false,
- isBusy: false,
- scrollOpts: {
- duration: 1500,
- offset: 0,
- easing: 'easeInOutCubic'
- }
- }
- },
- computed: {
- pageId: get('page/id'),
- permissions: get('page/effectivePermissions@comments'),
- isAuthenticated: get('user/authenticated'),
- userDisplayName: get('user/name')
- },
- methods: {
- onIntersect (entries, observer, isIntersecting) {
- if (isIntersecting) {
- this.fetch(true)
- }
- },
- async fetch (silent = false) {
- this.isLoading = true
- try {
- const results = await this.$apollo.query({
- query: gql`
- query ($locale: String!, $path: String!) {
- comments {
- list(locale: $locale, path: $path) {
- id
- render
- authorName
- createdAt
- updatedAt
- }
- }
- }
- `,
- variables: {
- locale: this.$store.get('page/locale'),
- path: this.$store.get('page/path')
- },
- fetchPolicy: 'network-only'
- })
- this.comments = _.get(results, 'data.comments.list', []).map(c => {
- const nameParts = c.authorName.toUpperCase().split(' ')
- let initials = _.head(nameParts).charAt(0)
- if (nameParts.length > 1) {
- initials += _.last(nameParts).charAt(0)
- }
- c.initials = initials
- return c
- })
- } catch (err) {
- console.warn(err)
- if (!silent) {
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- }
- }
- this.isLoading = false
- this.hasLoadedOnce = true
- },
- /**
- * Post New Comment
- */
- async postComment () {
- let rules = {
- comment: {
- presence: {
- allowEmpty: false
- },
- length: {
- minimum: 2
- }
- }
- }
- if (!this.isAuthenticated && this.permissions.write) {
- rules.name = {
- presence: {
- allowEmpty: false
- },
- length: {
- minimum: 2,
- maximum: 255
- }
- }
- rules.email = {
- presence: {
- allowEmpty: false
- },
- email: true
- }
- }
- const validationResults = validate({
- comment: this.newcomment,
- name: this.guestName,
- email: this.guestEmail
- }, rules, { format: 'flat' })
- if (validationResults) {
- this.$store.commit('showNotification', {
- style: 'red',
- message: validationResults[0],
- icon: 'alert'
- })
- return
- }
- try {
- const resp = await this.$apollo.mutate({
- mutation: gql`
- mutation (
- $pageId: Int!
- $replyTo: Int
- $content: String!
- $guestName: String
- $guestEmail: String
- ) {
- comments {
- create (
- pageId: $pageId
- replyTo: $replyTo
- content: $content
- guestName: $guestName
- guestEmail: $guestEmail
- ) {
- responseResult {
- succeeded
- errorCode
- slug
- message
- }
- id
- }
- }
- }
- `,
- variables: {
- pageId: this.pageId,
- replyTo: 0,
- content: this.newcomment,
- guestName: this.guestName,
- guestEmail: this.guestEmail
- }
- })
- if (_.get(resp, 'data.comments.create.responseResult.succeeded', false)) {
- this.$store.commit('showNotification', {
- style: 'success',
- message: this.$t('common:comments.postSuccess'),
- icon: 'check'
- })
- this.newcomment = ''
- await this.fetch()
- this.$nextTick(() => {
- this.$vuetify.goTo(`#comment-post-id-${_.get(resp, 'data.comments.create.id', 0)}`, this.scrollOpts)
- })
- } else {
- throw new Error(_.get(resp, 'data.comments.create.responseResult.message', 'An unexpected error occurred.'))
- }
- } catch (err) {
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- }
- },
- /**
- * Show Comment Editing Form
- */
- async editComment (cm) {
- this.$store.commit(`loadingStart`, 'comments-edit')
- this.isBusy = true
- try {
- const results = await this.$apollo.query({
- query: gql`
- query ($id: Int!) {
- comments {
- single(id: $id) {
- content
- }
- }
- }
- `,
- variables: {
- id: cm.id
- },
- fetchPolicy: 'network-only'
- })
- this.commentEditContent = _.get(results, 'data.comments.single.content', null)
- if (this.commentEditContent === null) {
- throw new Error('Failed to load comment content.')
- }
- } catch (err) {
- console.warn(err)
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- }
- this.commentEditId = cm.id
- this.isBusy = false
- this.$store.commit(`loadingStop`, 'comments-edit')
- },
- /**
- * Cancel Comment Edit
- */
- editCommentCancel () {
- this.commentEditId = 0
- this.commentEditContent = null
- },
- /**
- * Update Comment with new content
- */
- async updateComment () {
- this.$store.commit(`loadingStart`, 'comments-edit')
- this.isBusy = true
- try {
- if (this.commentEditContent.length < 2) {
- throw new Error(this.$t('common:comments.contentMissingError'))
- }
- const resp = await this.$apollo.mutate({
- mutation: gql`
- mutation (
- $id: Int!
- $content: String!
- ) {
- comments {
- update (
- id: $id,
- content: $content
- ) {
- responseResult {
- succeeded
- errorCode
- slug
- message
- }
- render
- }
- }
- }
- `,
- variables: {
- id: this.commentEditId,
- content: this.commentEditContent
- }
- })
- if (_.get(resp, 'data.comments.update.responseResult.succeeded', false)) {
- this.$store.commit('showNotification', {
- style: 'success',
- message: this.$t('common:comments.updateSuccess'),
- icon: 'check'
- })
- const cm = _.find(this.comments, ['id', this.commentEditId])
- cm.render = _.get(resp, 'data.comments.update.render', '-- Failed to load updated comment --')
- cm.updatedAt = (new Date()).toISOString()
- this.editCommentCancel()
- } else {
- throw new Error(_.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occurred.'))
- }
- } catch (err) {
- console.warn(err)
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- }
- this.isBusy = false
- this.$store.commit(`loadingStop`, 'comments-edit')
- },
- /**
- * Show Delete Comment Confirmation Dialog
- */
- deleteCommentConfirm (cm) {
- this.commentToDelete = cm
- this.deleteCommentDialogShown = true
- },
- /**
- * Delete Comment
- */
- async deleteComment () {
- this.$store.commit(`loadingStart`, 'comments-delete')
- this.isBusy = true
- this.deleteCommentDialogShown = false
- try {
- const resp = await this.$apollo.mutate({
- mutation: gql`
- mutation (
- $id: Int!
- ) {
- comments {
- delete (
- id: $id
- ) {
- responseResult {
- succeeded
- errorCode
- slug
- message
- }
- }
- }
- }
- `,
- variables: {
- id: this.commentToDelete.id
- }
- })
- if (_.get(resp, 'data.comments.delete.responseResult.succeeded', false)) {
- this.$store.commit('showNotification', {
- style: 'success',
- message: this.$t('common:comments.deleteSuccess'),
- icon: 'check'
- })
- this.comments = _.reject(this.comments, ['id', this.commentToDelete.id])
- } else {
- throw new Error(_.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occurred.'))
- }
- } catch (err) {
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- }
- this.isBusy = false
- this.$store.commit(`loadingStop`, 'comments-delete')
- }
- }
- }
- </script>
- <style lang="scss">
- .comments-post {
- position: relative;
- &:hover {
- .comments-post-actions {
- opacity: 1;
- }
- }
- &-actions {
- position: absolute;
- top: 16px;
- right: 16px;
- opacity: 0;
- transition: opacity .4s ease;
- }
- &-content {
- > p:first-child {
- padding-top: 0;
- }
- p {
- padding-top: 1rem;
- margin-bottom: 0;
- }
- img {
- max-width: 100%;
- border-radius: 5px;
- }
- code {
- background-color: rgba(mc('pink', '500'), .1);
- box-shadow: none;
- }
- pre > code {
- margin-top: 1rem;
- padding: 12px;
- background-color: #111;
- box-shadow: none;
- border-radius: 5px;
- width: 100%;
- color: #FFF;
- font-weight: 400;
- font-size: .85rem;
- font-family: Roboto Mono, monospace;
- }
- }
- }
- </style>
|