| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 | <template lang='pug'>  v-dialog(    v-model='isShown'    persistent    width='1000'    :fullscreen='$vuetify.breakpoint.smAndDown'    )    .dialog-header      v-icon(color='white') mdi-tag-text-outline      .subtitle-1.white--text.ml-3 {{$t('editor:props.pageProperties')}}      v-spacer      v-btn.mx-0(        outlined        dark        @click.native='close'        )        v-icon(left) mdi-check        span {{ $t('common:actions.ok') }}    v-card(tile)      v-tabs(color='white', background-color='blue darken-1', dark, centered, v-model='currentTab')        v-tab {{$t('editor:props.info')}}        v-tab {{$t('editor:props.scheduling')}}        v-tab(:disabled='!hasScriptPermission') {{$t('editor:props.scripts')}}        v-tab(disabled) {{$t('editor:props.social')}}        v-tab(:disabled='!hasStylePermission') {{$t('editor:props.styles')}}        v-tab-item(transition='fade-transition', reverse-transition='fade-transition')          v-card-text.pt-5            .overline.pb-5 {{$t('editor:props.pageInfo')}}            v-text-field(              ref='iptTitle'              outlined              :label='$t(`editor:props.title`)'              counter='255'              v-model='title'              )            v-text-field(              outlined              :label='$t(`editor:props.shortDescription`)'              counter='255'              v-model='description'              persistent-hint              :hint='$t(`editor:props.shortDescriptionHint`)'              )          v-divider          v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d3` : `lighten-5`')            .overline.pb-5 {{$t('editor:props.path')}}            v-container.pa-0(fluid, grid-list-lg)              v-layout(row, wrap)                v-flex(xs12, md2)                  v-select(                    outlined                    :label='$t(`editor:props.locale`)'                    suffix='/'                    :items='namespaces'                    v-model='locale'                    hide-details                  )                v-flex(xs12, md10)                  v-text-field(                    outlined                    :label='$t(`editor:props.path`)'                    append-icon='mdi-folder-search'                    v-model='path'                    :hint='$t(`editor:props.pathHint`)'                    persistent-hint                    @click:append='showPathSelector'                    :rules='[rules.required, rules.path]'                    )          v-divider          v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-4`')            .overline.pb-5 {{$t('editor:props.categorization')}}            v-chip-group.radius-5.mb-5(column, v-if='tags && tags.length > 0')              v-chip(                v-for='tag of tags'                :key='`tag-` + tag'                close                label                color='teal'                text-color='teal lighten-5'                @click:close='removeTag(tag)'                ) {{tag}}            v-combobox(              :label='$t(`editor:props.tags`)'              outlined              v-model='newTag'              :hint='$t(`editor:props.tagsHint`)'              :items='newTagSuggestions'              :loading='$apollo.queries.newTagSuggestions.loading'              persistent-hint              hide-no-data              :search-input.sync='newTagSearch'              )        v-tab-item(transition='fade-transition', reverse-transition='fade-transition')          v-card-text            .overline {{$t('editor:props.publishState')}}            v-switch(              :label='$t(`editor:props.publishToggle`)'              v-model='isPublished'              color='primary'              :hint='$t(`editor:props.publishToggleHint`)'              persistent-hint              inset              )          v-divider          v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d3` : `lighten-5`')            v-container.pa-0(fluid, grid-list-lg)              v-row                v-col(cols='6')                  v-dialog(                    ref='menuPublishStart'                    :close-on-content-click='false'                    v-model='isPublishStartShown'                    :return-value.sync='publishStartDate'                    width='460px'                    :disabled='!isPublished'                    )                    template(v-slot:activator='{ on }')                      v-text-field(                        v-on='on'                        :label='$t(`editor:props.publishStart`)'                        v-model='publishStartDate'                        prepend-icon='mdi-calendar-check'                        readonly                        outlined                        clearable                        :hint='$t(`editor:props.publishStartHint`)'                        persistent-hint                        :disabled='!isPublished'                        )                    v-date-picker(                      v-model='publishStartDate'                      :min='(new Date()).toISOString().substring(0, 10)'                      color='primary'                      reactive                      scrollable                      landscape                      )                      v-spacer                      v-btn(                        text                        color='primary'                        @click='isPublishStartShown = false'                        ) {{$t('common:actions.cancel')}}                      v-btn(                        text                        color='primary'                        @click='$refs.menuPublishStart.save(publishStartDate)'                        ) {{$t('common:actions.ok')}}                v-col(cols='6')                  v-dialog(                    ref='menuPublishEnd'                    :close-on-content-click='false'                    v-model='isPublishEndShown'                    :return-value.sync='publishEndDate'                    width='460px'                    :disabled='!isPublished'                    )                    template(v-slot:activator='{ on }')                      v-text-field(                        v-on='on'                        :label='$t(`editor:props.publishEnd`)'                        v-model='publishEndDate'                        prepend-icon='mdi-calendar-remove'                        readonly                        outlined                        clearable                        :hint='$t(`editor:props.publishEndHint`)'                        persistent-hint                        :disabled='!isPublished'                        )                    v-date-picker(                      v-model='publishEndDate'                      :min='(new Date()).toISOString().substring(0, 10)'                      color='primary'                      reactive                      scrollable                      landscape                      )                      v-spacer                      v-btn(                        text                        color='primary'                        @click='isPublishEndShown = false'                        ) {{$t('common:actions.cancel')}}                      v-btn(                        text                        color='primary'                        @click='$refs.menuPublishEnd.save(publishEndDate)'                        ) {{$t('common:actions.ok')}}        v-tab-item(:transition='false', :reverse-transition='false')          .editor-props-codeeditor-title            .overline {{$t('editor:props.html')}}          .editor-props-codeeditor            textarea(ref='codejs')          .editor-props-codeeditor-hint            .caption {{$t('editor:props.htmlHint')}}        v-tab-item(transition='fade-transition', reverse-transition='fade-transition')          v-card-text            .overline {{$t('editor:props.socialFeatures')}}            v-switch(              :label='$t(`editor:props.allowComments`)'              v-model='isPublished'              color='primary'              :hint='$t(`editor:props.allowCommentsHint`)'              persistent-hint              inset              )            v-switch(              :label='$t(`editor:props.allowRatings`)'              v-model='isPublished'              color='primary'              :hint='$t(`editor:props.allowRatingsHint`)'              persistent-hint              disabled              inset              )            v-switch(              :label='$t(`editor:props.displayAuthor`)'              v-model='isPublished'              color='primary'              :hint='$t(`editor:props.displayAuthorHint`)'              persistent-hint              inset              )            v-switch(              :label='$t(`editor:props.displaySharingBar`)'              v-model='isPublished'              color='primary'              :hint='$t(`editor:props.displaySharingBarHint`)'              persistent-hint              inset              )        v-tab-item(:transition='false', :reverse-transition='false')          .editor-props-codeeditor-title            .overline {{$t('editor:props.css')}}          .editor-props-codeeditor            textarea(ref='codecss')          .editor-props-codeeditor-hint            .caption {{$t('editor:props.cssHint')}}    page-selector(:mode='pageSelectorMode', v-model='pageSelectorShown', :path='path', :locale='locale', :open-handler='setPath')</template><script>import _ from 'lodash'import { sync, get } from 'vuex-pathify'import gql from 'graphql-tag'import CodeMirror from 'codemirror'import 'codemirror/lib/codemirror.css'import 'codemirror/mode/htmlmixed/htmlmixed.js'import 'codemirror/mode/css/css.js'/* global siteLangs, siteConfig */const filenamePattern = /^(?![\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s])(?!.*[\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]$)[^\#\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]*$/export default {  props: {    value: {      type: Boolean,      default: false    }  },  data () {    return {      isPublishStartShown: false,      isPublishEndShown: false,      pageSelectorShown: false,      namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang],      newTag: '',      newTagSuggestions: [],      newTagSearch: '',      currentTab: 0,      cm: null,      rules: {          required: value => !!value || 'This field is required.',          path: value => {            return filenamePattern.test(value) || 'Invalid path. Please ensure it does not contain special characters, or begin/end in a slash or hashtag string.'          }      }    }  },  computed: {    isShown: {      get() { return this.value },      set(val) { this.$emit('input', val) }    },    mode: get('editor/mode'),    title: sync('page/title'),    description: sync('page/description'),    locale: sync('page/locale'),    tags: sync('page/tags'),    path: sync('page/path'),    isPublished: sync('page/isPublished'),    publishStartDate: sync('page/publishStartDate'),    publishEndDate: sync('page/publishEndDate'),    scriptJs: sync('page/scriptJs'),    scriptCss: sync('page/scriptCss'),    hasScriptPermission: get('page/effectivePermissions@pages.script'),    hasStylePermission: get('page/effectivePermissions@pages.style'),    pageSelectorMode () {      return (this.mode === 'create') ? 'create' : 'move'    }  },  watch: {    value (newValue, oldValue) {      if (newValue) {        _.delay(() => {          this.$refs.iptTitle.focus()        }, 500)      }    },    newTag (newValue, oldValue) {      const tagClean = _.trim(newValue || '').toLowerCase()      if (tagClean && tagClean.length > 0) {        if (!_.includes(this.tags, tagClean)) {          this.tags = [...this.tags, tagClean]        }        this.$nextTick(() => {          this.newTag = null        })      }    },    currentTab (newValue, oldValue) {      if (this.cm) {        this.cm.toTextArea()      }      if (newValue === 2) {        this.$nextTick(() => {          setTimeout(() => {            this.loadEditor(this.$refs.codejs, 'html')          }, 100)        })      } else if (newValue === 4) {        this.$nextTick(() => {          setTimeout(() => {            this.loadEditor(this.$refs.codecss, 'css')          }, 100)        })      }    }  },  methods: {    removeTag (tag) {      this.tags = _.without(this.tags, tag)    },    close() {      this.isShown = false    },    showPathSelector() {      this.pageSelectorShown = true    },    setPath({ path, locale }) {      this.locale = locale      this.path = path    },    loadEditor(ref, mode) {      this.cm = CodeMirror.fromTextArea(ref, {        tabSize: 2,        mode: `text/${mode}`,        theme: 'wikijs-dark',        lineNumbers: true,        lineWrapping: true,        line: true,        styleActiveLine: true,        viewportMargin: 50,        inputStyle: 'contenteditable',        direction: 'ltr'      })      switch (mode) {        case 'html':          this.cm.setValue(this.scriptJs)          this.cm.on('change', c => {            this.scriptJs = c.getValue()          })          break        case 'css':          this.cm.setValue(this.scriptCss)          this.cm.on('change', c => {            this.scriptCss = c.getValue()          })          break        default:          console.warn('Invalid Editor Mode')          break      }      this.cm.setSize(null, '500px')      this.$nextTick(() => {        this.cm.refresh()        this.cm.focus()      })    }  },  apollo: {    newTagSuggestions: {      query: gql`        query ($query: String!) {          pages {            searchTags (query: $query)          }        }      `,      variables () {        return {          query: this.newTagSearch        }      },      fetchPolicy: 'cache-first',      update: (data) => _.get(data, 'pages.searchTags', []),      skip () {        return !this.value || _.isEmpty(this.newTagSearch)      },      throttle: 500    }  }}</script><style lang='scss'>.editor-props-codeeditor {  background-color: mc('grey', '900');  min-height: 500px;  > textarea {    visibility: hidden;  }  &-title {    background-color: mc('grey', '900');    border-bottom: 1px solid lighten(mc('grey', '900'), 10%);    color: #FFF;    padding: 10px;  }  &-hint {    background-color: mc('grey', '900');    border-top: 1px solid lighten(mc('grey', '900'), 5%);    color: mc('grey', '500');    padding: 5px 10px;  }}</style>
 |