mirror of https://github.com/requarks/wiki
parent
9fb9f53d53
commit
b979e50830
@ -1,38 +0,0 @@
|
||||
{
|
||||
"comments": true,
|
||||
"plugins": [
|
||||
"lodash",
|
||||
"graphql-tag",
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-syntax-import-meta",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-json-strings",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
"legacy": true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-function-sent",
|
||||
"@babel/plugin-proposal-export-namespace-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-throw-expressions",
|
||||
[
|
||||
"prismjs", {
|
||||
"languages": ["clike", "markup"],
|
||||
"plugins": ["line-numbers", "autoloader", "normalize-whitespace", "copy-to-clipboard", "toolbar"],
|
||||
"theme": "twilight",
|
||||
"css": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env", {
|
||||
"useBuiltIns": "entry",
|
||||
"corejs": 3,
|
||||
"debug": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
classPrefix: 'mdz-',
|
||||
options: ['setClasses'],
|
||||
'feature-detects': [
|
||||
'css/backdropfilter'
|
||||
]
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
/* global siteConfig */
|
||||
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import VueClipboards from 'vue-clipboards'
|
||||
import { ApolloClient } from 'apollo-client'
|
||||
import { BatchHttpLink } from 'apollo-link-batch-http'
|
||||
import { ApolloLink } from 'apollo-link'
|
||||
import { ErrorLink } from 'apollo-link-error'
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||
import VueApollo from 'vue-apollo'
|
||||
import Vuetify from 'vuetify/lib'
|
||||
import Velocity from 'velocity-animate'
|
||||
import Vuescroll from 'vuescroll/dist/vuescroll-native'
|
||||
import Hammer from 'hammerjs'
|
||||
import moment from 'moment-timezone'
|
||||
import VueMoment from 'vue-moment'
|
||||
import store from './store'
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
// ====================================
|
||||
// Load Modules
|
||||
// ====================================
|
||||
|
||||
import boot from './modules/boot'
|
||||
import localization from './modules/localization'
|
||||
|
||||
// ====================================
|
||||
// Load Helpers
|
||||
// ====================================
|
||||
|
||||
import helpers from './helpers'
|
||||
|
||||
// ====================================
|
||||
// Initialize Global Vars
|
||||
// ====================================
|
||||
|
||||
window.WIKI = null
|
||||
window.boot = boot
|
||||
window.Hammer = Hammer
|
||||
|
||||
moment.locale(siteConfig.lang)
|
||||
|
||||
store.commit('user/REFRESH_AUTH')
|
||||
|
||||
// ====================================
|
||||
// Initialize Apollo Client (GraphQL)
|
||||
// ====================================
|
||||
|
||||
const graphQLEndpoint = window.location.protocol + '//' + window.location.host + '/_graphql'
|
||||
|
||||
const graphQLLink = ApolloLink.from([
|
||||
new ErrorLink(({ graphQLErrors, networkError }) => {
|
||||
if (graphQLErrors) {
|
||||
let isAuthError = false
|
||||
graphQLErrors.map(({ message, locations, path }) => {
|
||||
if (message === `Forbidden`) {
|
||||
isAuthError = true
|
||||
}
|
||||
console.error(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
|
||||
})
|
||||
store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: isAuthError ? `You are not authorized to access this resource.` : `An unexpected error occurred.`,
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
if (networkError) {
|
||||
console.error(networkError)
|
||||
store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: `Network Error: ${networkError.message}`,
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
}),
|
||||
new BatchHttpLink({
|
||||
includeExtensions: true,
|
||||
uri: graphQLEndpoint,
|
||||
credentials: 'include',
|
||||
fetch: async (uri, options) => {
|
||||
// Strip __typename fields from variables
|
||||
let body = JSON.parse(options.body)
|
||||
body = body.map(bd => {
|
||||
return ({
|
||||
...bd,
|
||||
variables: JSON.parse(JSON.stringify(bd.variables), (key, value) => { return key === '__typename' ? undefined : value })
|
||||
})
|
||||
})
|
||||
options.body = JSON.stringify(body)
|
||||
|
||||
// Inject authentication token
|
||||
const jwtToken = Cookies.get('jwt')
|
||||
if (jwtToken) {
|
||||
options.headers.Authorization = `Bearer ${jwtToken}`
|
||||
}
|
||||
|
||||
const resp = await fetch(uri, options)
|
||||
|
||||
// Handle renewed JWT
|
||||
const newJWT = resp.headers.get('new-jwt')
|
||||
if (newJWT) {
|
||||
Cookies.set('jwt', newJWT, { expires: 365 })
|
||||
}
|
||||
return resp
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
window.graphQL = new ApolloClient({
|
||||
link: graphQLLink,
|
||||
cache: new InMemoryCache(),
|
||||
connectToDevTools: (process.env.node_env === 'development')
|
||||
})
|
||||
|
||||
// ====================================
|
||||
// Initialize Vue Modules
|
||||
// ====================================
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(VueApollo)
|
||||
Vue.use(VueClipboards)
|
||||
Vue.use(localization.VueI18Next)
|
||||
Vue.use(helpers)
|
||||
Vue.use(Vuetify)
|
||||
Vue.use(VueMoment, { moment })
|
||||
Vue.use(Vuescroll)
|
||||
|
||||
Vue.prototype.Velocity = Velocity
|
||||
|
||||
// ====================================
|
||||
// Register Vue Components
|
||||
// ====================================
|
||||
|
||||
Vue.component('Comments', () => import(/* webpackChunkName: "comments" */ './components/comments.vue'))
|
||||
Vue.component('Editor', () => import(/* webpackPrefetch: -100, webpackChunkName: "editor" */ './components/editor.vue'))
|
||||
Vue.component('History', () => import(/* webpackChunkName: "history" */ './components/history.vue'))
|
||||
Vue.component('Loader', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/loader.vue'))
|
||||
Vue.component('NavHeader', () => import(/* webpackMode: "eager" */ './components/common/nav-header.vue'))
|
||||
Vue.component('NewPage', () => import(/* webpackChunkName: "new-page" */ './components/new-page.vue'))
|
||||
Vue.component('Notify', () => import(/* webpackMode: "eager" */ './components/common/notify.vue'))
|
||||
Vue.component('NotFound', () => import(/* webpackChunkName: "not-found" */ './components/not-found.vue'))
|
||||
Vue.component('PageSelector', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/page-selector.vue'))
|
||||
Vue.component('PageSource', () => import(/* webpackChunkName: "source" */ './components/source.vue'))
|
||||
Vue.component('SearchResults', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/search-results.vue'))
|
||||
Vue.component('SocialSharing', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/social-sharing.vue'))
|
||||
Vue.component('Tags', () => import(/* webpackChunkName: "tags" */ './components/tags.vue'))
|
||||
Vue.component('Unauthorized', () => import(/* webpackChunkName: "unauthorized" */ './components/unauthorized.vue'))
|
||||
Vue.component('VCardChin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue'))
|
||||
Vue.component('VCardInfo', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-info.vue'))
|
||||
|
||||
Vue.component('NavFooter', () => import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/components/nav-footer.vue'))
|
||||
Vue.component('Page', () => import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/components/page.vue'))
|
||||
|
||||
let bootstrap = () => {
|
||||
// ====================================
|
||||
// Notifications
|
||||
// ====================================
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
store.dispatch('startLoading')
|
||||
})
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: window.graphQL
|
||||
})
|
||||
|
||||
// ====================================
|
||||
// Bootstrap Vue
|
||||
// ====================================
|
||||
|
||||
const i18n = localization.init()
|
||||
|
||||
let darkModeEnabled = siteConfig.darkMode
|
||||
if ((store.get('user/appearance') || '').length > 0) {
|
||||
darkModeEnabled = (store.get('user/appearance') === 'dark')
|
||||
}
|
||||
|
||||
window.WIKI = new Vue({
|
||||
el: '#root',
|
||||
components: {},
|
||||
mixins: [helpers],
|
||||
apolloProvider,
|
||||
store,
|
||||
i18n,
|
||||
vuetify: new Vuetify({
|
||||
rtl: siteConfig.rtl,
|
||||
theme: {
|
||||
dark: darkModeEnabled
|
||||
}
|
||||
}),
|
||||
mounted () {
|
||||
this.$moment.locale(siteConfig.lang)
|
||||
if ((store.get('user/dateFormat') || '').length > 0) {
|
||||
this.$moment.updateLocale(this.$moment.locale(), {
|
||||
longDateFormat: {
|
||||
'L': store.get('user/dateFormat')
|
||||
}
|
||||
})
|
||||
}
|
||||
if ((store.get('user/timezone') || '').length > 0) {
|
||||
this.$moment.tz.setDefault(store.get('user/timezone'))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// ----------------------------------
|
||||
// Dispatch boot ready
|
||||
// ----------------------------------
|
||||
|
||||
window.boot.notify('vue')
|
||||
}
|
||||
|
||||
window.boot.onDOMReady(bootstrap)
|
@ -1,553 +0,0 @@
|
||||
<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>
|
@ -1,119 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-toolbar.radius-7(flat, :color='$vuetify.theme.dark ? "grey darken-4-l3" : "grey lighten-3"')
|
||||
.body-2.mr-3 {{$t('common:duration.every')}}
|
||||
v-text-field(
|
||||
solo
|
||||
hide-details
|
||||
flat
|
||||
reverse
|
||||
v-model='minutes'
|
||||
style='flex: 1 1 70px;'
|
||||
)
|
||||
.body-2.mx-3 {{$t('common:duration.minutes')}}
|
||||
v-divider.mr-3
|
||||
v-text-field(
|
||||
solo
|
||||
hide-details
|
||||
flat
|
||||
reverse
|
||||
v-model='hours'
|
||||
style='flex: 1 1 70px;'
|
||||
)
|
||||
.body-2.mx-3 {{$t('common:duration.hours')}}
|
||||
v-divider.mr-3
|
||||
v-text-field(
|
||||
solo
|
||||
hide-details
|
||||
flat
|
||||
reverse
|
||||
v-model='days'
|
||||
style='flex: 1 1 70px;'
|
||||
)
|
||||
.body-2.mx-3 {{$t('common:duration.days')}}
|
||||
v-divider.mr-3
|
||||
v-text-field(
|
||||
solo
|
||||
hide-details
|
||||
flat
|
||||
reverse
|
||||
v-model='months'
|
||||
style='flex: 1 1 70px;'
|
||||
)
|
||||
.body-2.mx-3 {{$t('common:duration.months')}}
|
||||
v-divider.mr-3
|
||||
v-text-field(
|
||||
solo
|
||||
hide-details
|
||||
flat
|
||||
reverse
|
||||
v-model='years'
|
||||
style='flex: 1 1 70px;'
|
||||
)
|
||||
.body-2.mx-3 {{$t('common:duration.years')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: 'PT5M'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
duration: moment.duration(0)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
years: {
|
||||
get() { return this.duration.years() || 0 },
|
||||
set(val) { this.rebuild(_.toNumber(val), 'years') }
|
||||
},
|
||||
months: {
|
||||
get() { return this.duration.months() || 0 },
|
||||
set(val) { this.rebuild(_.toNumber(val), 'months') }
|
||||
},
|
||||
days: {
|
||||
get() { return this.duration.days() || 0 },
|
||||
set(val) { this.rebuild(_.toNumber(val), 'days') }
|
||||
},
|
||||
hours: {
|
||||
get() { return this.duration.hours() || 0 },
|
||||
set(val) { this.rebuild(_.toNumber(val), 'hours') }
|
||||
},
|
||||
minutes: {
|
||||
get() { return this.duration.minutes() || 0 },
|
||||
set(val) { this.rebuild(_.toNumber(val), 'minutes') }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue, oldValue) {
|
||||
this.duration = moment.duration(newValue)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rebuild(val, unit) {
|
||||
if (!_.isFinite(val) || val < 0) {
|
||||
val = 0
|
||||
}
|
||||
const newDuration = {
|
||||
minutes: this.duration.minutes(),
|
||||
hours: this.duration.hours(),
|
||||
days: this.duration.days(),
|
||||
months: this.duration.months(),
|
||||
years: this.duration.years()
|
||||
}
|
||||
_.set(newDuration, unit, val)
|
||||
this.duration = moment.duration(newDuration)
|
||||
this.$emit('input', this.duration.toISOString())
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.duration = moment.duration(this.value)
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,67 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-dialog(v-model='value', persistent, max-width='350', :overlay-color='color', overlay-opacity='.7')
|
||||
v-card.loader-dialog.radius-7(:color='color', dark)
|
||||
v-card-text.text-center.py-4
|
||||
atom-spinner.is-inline(
|
||||
v-if='mode === `loading`'
|
||||
:animation-duration='1000'
|
||||
:size='60'
|
||||
color='#FFF'
|
||||
)
|
||||
img(v-else-if='mode === `icon`', :src='`/_assets-legacy/svg/icon-` + icon + `.svg`', :alt='icon')
|
||||
.subtitle-1.white--text {{ title }}
|
||||
.caption {{ subtitle }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AtomSpinner } from 'epic-spinners'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AtomSpinner
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'blue darken-3'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Working...'
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: 'Please wait'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'loading'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'checkmark'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.loader-dialog {
|
||||
transition: all .4s ease;
|
||||
|
||||
.atom-spinner.is-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
.caption {
|
||||
color: rgba(255,255,255,.7);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,566 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-app-bar.nav-header(color='black', dark, app, :clipped-left='!$vuetify.rtl', :clipped-right='$vuetify.rtl', fixed, flat, :extended='searchIsShown && $vuetify.breakpoint.smAndDown')
|
||||
v-toolbar(color='deep-purple', flat, slot='extension', v-if='searchIsShown && $vuetify.breakpoint.smAndDown')
|
||||
v-text-field(
|
||||
ref='searchFieldMobile'
|
||||
v-model='search'
|
||||
clearable
|
||||
background-color='deep-purple'
|
||||
color='white'
|
||||
:label='$t(`common:header.search`)'
|
||||
single-line
|
||||
solo
|
||||
flat
|
||||
hide-details
|
||||
prepend-inner-icon='mdi-magnify'
|
||||
:loading='searchIsLoading'
|
||||
@keyup.enter='searchEnter'
|
||||
autocomplete='none'
|
||||
)
|
||||
v-layout(row)
|
||||
v-flex(xs5, md4)
|
||||
v-toolbar.nav-header-inner(color='black', dark, flat, :class='$vuetify.rtl ? `pr-3` : `pl-3`')
|
||||
v-avatar(tile, size='34', @click='goHome')
|
||||
v-img.org-logo(:src='logoUrl')
|
||||
//- v-menu(open-on-hover, offset-y, bottom, left, min-width='250', transition='slide-y-transition')
|
||||
//- template(v-slot:activator='{ on }')
|
||||
//- v-app-bar-nav-icon.btn-animate-app(v-on='on', :class='$vuetify.rtl ? `mx-0` : ``')
|
||||
//- v-icon mdi-menu
|
||||
//- v-list(nav, :light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark', :class='$vuetify.theme.dark ? `grey darken-4` : ``')
|
||||
//- v-list-item.pl-4(href='/')
|
||||
//- v-list-item-avatar(size='24'): v-icon(color='blue') mdi-home
|
||||
//- v-list-item-title.body-2 {{$t('common:header.home')}}
|
||||
//- v-list-item.pl-4(@click='')
|
||||
//- v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-file-tree
|
||||
//- v-list-item-content
|
||||
//- v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.siteMap')}}
|
||||
//- v-list-item-subtitle.overline.grey--text.text--lighten-2 Coming soon
|
||||
//- v-list-item.pl-4(href='/t')
|
||||
//- v-list-item-avatar(size='24'): v-icon(color='teal') mdi-tag-multiple
|
||||
//- v-list-item-title.body-2 {{$t('common:header.browseTags')}}
|
||||
//- v-list-item.pl-4(@click='assets')
|
||||
//- v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-folder-multiple-image
|
||||
//- v-list-item-content
|
||||
//- v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.imagesFiles')}}
|
||||
//- v-list-item-subtitle.overline.grey--text.text--lighten-2 Coming soon
|
||||
v-toolbar-title(:class='{ "mx-3": $vuetify.breakpoint.mdAndUp, "mx-1": $vuetify.breakpoint.smAndDown }')
|
||||
span.subheading {{title}}
|
||||
v-flex(md4, v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-toolbar.nav-header-inner(color='black', dark, flat)
|
||||
slot(name='mid')
|
||||
transition(name='navHeaderSearch', v-if='searchIsShown')
|
||||
v-text-field(
|
||||
ref='searchField',
|
||||
v-if='searchIsShown && $vuetify.breakpoint.mdAndUp',
|
||||
v-model='search',
|
||||
color='white',
|
||||
:label='$t(`common:header.search`)',
|
||||
single-line,
|
||||
solo
|
||||
flat
|
||||
rounded
|
||||
hide-details,
|
||||
prepend-inner-icon='mdi-magnify',
|
||||
:loading='searchIsLoading',
|
||||
@keyup.enter='searchEnter'
|
||||
@keyup.esc='searchClose'
|
||||
@focus='searchFocus'
|
||||
@blur='searchBlur'
|
||||
@keyup.down='searchMove(`down`)'
|
||||
@keyup.up='searchMove(`up`)'
|
||||
autocomplete='none'
|
||||
)
|
||||
v-tooltip(bottom)
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.ml-2.mr-0(icon, v-on='on', href='/t', :aria-label='$t(`common:header.browseTags`)')
|
||||
v-icon(color='grey') mdi-tag-multiple
|
||||
span {{$t('common:header.browseTags')}}
|
||||
v-flex(xs7, md4)
|
||||
v-toolbar.nav-header-inner.pr-4(color='black', dark, flat)
|
||||
v-spacer
|
||||
.navHeaderLoading.mr-3
|
||||
v-progress-circular(indeterminate, color='blue', :size='22', :width='2' v-show='isLoading')
|
||||
|
||||
slot(name='actions')
|
||||
|
||||
//- (mobile) SEARCH TOGGLE
|
||||
|
||||
v-btn(
|
||||
v-if='!hideSearch && $vuetify.breakpoint.smAndDown'
|
||||
@click='searchToggle'
|
||||
icon
|
||||
)
|
||||
v-icon(color='grey') mdi-magnify
|
||||
|
||||
//- LANGUAGES
|
||||
|
||||
template(v-if='mode === `view` && locales.length > 0')
|
||||
v-menu(offset-y, bottom, transition='slide-y-transition', max-height='320px', min-width='210px', left)
|
||||
template(v-slot:activator='{ on: menu, attrs }')
|
||||
v-tooltip(bottom)
|
||||
template(v-slot:activator='{ on: tooltip }')
|
||||
v-btn(
|
||||
icon
|
||||
v-bind='attrs'
|
||||
v-on='{ ...menu, ...tooltip }'
|
||||
:class='$vuetify.rtl ? `ml-3` : ``'
|
||||
tile
|
||||
height='64'
|
||||
:aria-label='$t(`common:header.language`)'
|
||||
)
|
||||
v-icon(color='grey') mdi-web
|
||||
span {{$t('common:header.language')}}
|
||||
v-list(nav)
|
||||
template(v-for='(lc, idx) of locales')
|
||||
v-list-item(@click='changeLocale(lc)')
|
||||
v-list-item-action(style='min-width:auto;'): v-chip(:color='lc.code === locale ? `blue` : `grey`', small, label, dark) {{lc.code.toUpperCase()}}
|
||||
v-list-item-title {{lc.name}}
|
||||
v-divider(vertical)
|
||||
|
||||
//- PAGE ACTIONS
|
||||
|
||||
template(v-if='hasAnyPagePermissions && path && mode !== `edit`')
|
||||
v-menu(offset-y, bottom, transition='slide-y-transition', left)
|
||||
template(v-slot:activator='{ on: menu, attrs }')
|
||||
v-tooltip(bottom)
|
||||
template(v-slot:activator='{ on: tooltip }')
|
||||
v-btn(
|
||||
icon
|
||||
v-bind='attrs'
|
||||
v-on='{ ...menu, ...tooltip }'
|
||||
:class='$vuetify.rtl ? `ml-3` : ``'
|
||||
tile
|
||||
height='64'
|
||||
:aria-label='$t(`common:header.pageActions`)'
|
||||
)
|
||||
v-icon(color='grey') mdi-file-document-edit-outline
|
||||
span {{$t('common:header.pageActions')}}
|
||||
v-list(nav, :light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark', :class='$vuetify.theme.dark ? `grey darken-4` : ``')
|
||||
.overline.pa-4.grey--text {{$t('common:header.currentPage')}}
|
||||
v-list-item.pl-4(@click='pageView', v-if='mode !== `view`')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-file-document-outline
|
||||
v-list-item-title.body-2 {{$t('common:header.view')}}
|
||||
v-list-item.pl-4(@click='pageEdit', v-if='mode !== `edit` && hasWritePagesPermission')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-file-document-edit-outline
|
||||
v-list-item-title.body-2 {{$t('common:header.edit')}}
|
||||
v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history` && hasReadHistoryPermission')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-history
|
||||
v-list-item-content
|
||||
v-list-item-title.body-2 {{$t('common:header.history')}}
|
||||
v-list-item.pl-4(@click='pageSource', v-if='mode !== `source` && hasReadSourcePermission')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-code-tags
|
||||
v-list-item-title.body-2 {{$t('common:header.viewSource')}}
|
||||
v-list-item.pl-4(@click='pageConvert', v-if='hasWritePagesPermission')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-lightning-bolt
|
||||
v-list-item-title.body-2 {{$t('common:header.convert')}}
|
||||
v-list-item.pl-4(@click='pageDuplicate', v-if='hasWritePagesPermission')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-content-duplicate
|
||||
v-list-item-title.body-2 {{$t('common:header.duplicate')}}
|
||||
v-list-item.pl-4(@click='pageMove', v-if='hasManagePagesPermission')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-content-save-move-outline
|
||||
v-list-item-content
|
||||
v-list-item-title.body-2 {{$t('common:header.move')}}
|
||||
v-list-item.pl-4(@click='pageDelete', v-if='hasDeletePagesPermission')
|
||||
v-list-item-avatar(size='24', tile): v-icon(color='red darken-2') mdi-trash-can-outline
|
||||
v-list-item-title.body-2 {{$t('common:header.delete')}}
|
||||
v-divider(vertical)
|
||||
|
||||
//- NEW PAGE
|
||||
|
||||
template(v-if='hasNewPagePermission && path && mode !== `edit`')
|
||||
v-tooltip(bottom)
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon, tile, height='64', v-on='on', @click='pageNew', :aria-label='$t(`common:header.newPage`)')
|
||||
v-icon(color='grey') mdi-text-box-plus-outline
|
||||
span {{$t('common:header.newPage')}}
|
||||
v-divider(vertical)
|
||||
|
||||
//- ADMIN
|
||||
|
||||
template(v-if='isAuthenticated && isAdmin')
|
||||
v-tooltip(bottom, v-if='mode !== `admin`')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon, tile, height='64', v-on='on', href='/a', :aria-label='$t(`common:header.admin`)')
|
||||
v-icon(color='grey') mdi-cog
|
||||
span {{$t('common:header.admin')}}
|
||||
v-btn(v-else, text, tile, height='64', href='/', :aria-label='$t(`common:actions.exit`)')
|
||||
v-icon(left, color='grey') mdi-exit-to-app
|
||||
span {{$t('common:actions.exit')}}
|
||||
v-divider(vertical)
|
||||
|
||||
//- ACCOUNT
|
||||
|
||||
v-menu(v-if='isAuthenticated', offset-y, bottom, min-width='300', transition='slide-y-transition', left)
|
||||
template(v-slot:activator='{ on: menu, attrs }')
|
||||
v-tooltip(bottom)
|
||||
template(v-slot:activator='{ on: tooltip }')
|
||||
v-btn(
|
||||
icon
|
||||
v-bind='attrs'
|
||||
v-on='{ ...menu, ...tooltip }'
|
||||
:class='$vuetify.rtl ? `ml-0` : ``'
|
||||
tile
|
||||
height='64'
|
||||
:aria-label='$t(`common:header.account`)'
|
||||
)
|
||||
v-icon(v-if='picture.kind === `initials`', color='grey') mdi-account-circle
|
||||
v-avatar(v-else-if='picture.kind === `image`', :size='34')
|
||||
v-img(:src='picture.url')
|
||||
span {{$t('common:header.account')}}
|
||||
v-list(nav)
|
||||
v-list-item.py-3.grey(:class='$vuetify.theme.dark ? `darken-4-l5` : `lighten-5`')
|
||||
v-list-item-avatar
|
||||
v-avatar.blue(v-if='picture.kind === `initials`', :size='40')
|
||||
span.white--text.subheading {{picture.initials}}
|
||||
v-avatar(v-else-if='picture.kind === `image`', :size='40')
|
||||
v-img(:src='picture.url')
|
||||
v-list-item-content
|
||||
v-list-item-title {{name}}
|
||||
v-list-item-subtitle {{email}}
|
||||
//- v-list-item(href='/w', disabled)
|
||||
//- v-list-item-action: v-icon(color='blue') mdi-view-compact-outline
|
||||
//- v-list-item-content
|
||||
//- v-list-item-title {{$t('common:header.myWiki')}}
|
||||
//- v-list-item-subtitle.overline Coming soon
|
||||
v-list-item(href='/_profile')
|
||||
v-list-item-action: v-icon(color='blue-grey') mdi-face-profile
|
||||
v-list-item-content
|
||||
v-list-item-title(:class='$vuetify.theme.dark ? `blue-grey--text text--lighten-3` : `blue-grey--text`') {{$t('common:header.profile')}}
|
||||
v-list-item(href='/logout')
|
||||
v-list-item-action: v-icon(color='red') mdi-logout
|
||||
v-list-item-title.red--text {{$t('common:header.logout')}}
|
||||
|
||||
v-tooltip(v-else, left)
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon, v-on='on', color='grey darken-3', href='/login', :aria-label='$t(`common:header.login`)')
|
||||
v-icon(color='grey') mdi-account-circle
|
||||
span {{$t('common:header.login')}}
|
||||
|
||||
page-selector(mode='create', v-model='newPageModal', :open-handler='pageNewCreate', :locale='locale')
|
||||
page-selector(mode='move', v-model='movePageModal', :open-handler='pageMoveRename', :path='path', :locale='locale')
|
||||
page-selector(mode='create', v-model='duplicateOpts.modal', :open-handler='pageDuplicateHandle', :path='duplicateOpts.path', :locale='duplicateOpts.locale')
|
||||
page-delete(v-model='deletePageModal', v-if='path && path.length')
|
||||
page-convert(v-model='convertPageModal', v-if='path && path.length')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
import _ from 'lodash'
|
||||
|
||||
import movePageMutation from 'gql/common/common-pages-mutation-move.gql'
|
||||
|
||||
/* global siteConfig, siteLangs */
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageDelete: () => import('./page-delete.vue'),
|
||||
PageConvert: () => import('./page-convert.vue')
|
||||
},
|
||||
props: {
|
||||
dense: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuIsShown: true,
|
||||
searchIsShown: true,
|
||||
searchAdvMenuShown: false,
|
||||
newPageModal: false,
|
||||
movePageModal: false,
|
||||
convertPageModal: false,
|
||||
deletePageModal: false,
|
||||
locales: siteLangs,
|
||||
isDevMode: false,
|
||||
duplicateOpts: {
|
||||
locale: 'en',
|
||||
path: 'new-page',
|
||||
modal: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
search: sync('site/search'),
|
||||
searchIsFocused: sync('site/searchIsFocused'),
|
||||
searchIsLoading: sync('site/searchIsLoading'),
|
||||
searchRestrictLocale: sync('site/searchRestrictLocale'),
|
||||
searchRestrictPath: sync('site/searchRestrictPath'),
|
||||
isLoading: get('isLoading'),
|
||||
title: get('site/title'),
|
||||
logoUrl: get('site/logoUrl'),
|
||||
path: get('page/path'),
|
||||
locale: get('page/locale'),
|
||||
mode: get('page/mode'),
|
||||
name: get('user/name'),
|
||||
email: get('user/email'),
|
||||
pictureUrl: get('user/pictureUrl'),
|
||||
isAuthenticated: get('user/authenticated'),
|
||||
permissions: get('user/permissions'),
|
||||
picture () {
|
||||
if (this.pictureUrl && this.pictureUrl.length > 1) {
|
||||
return {
|
||||
kind: 'image',
|
||||
url: (this.pictureUrl === 'internal') ? `/_userav/${this.$store.get('user/id')}` : this.pictureUrl
|
||||
}
|
||||
} else {
|
||||
const nameParts = ['X', 'X'] // this.name.toUpperCase().split(' ')
|
||||
let initials = _.head(nameParts).charAt(0)
|
||||
if (nameParts.length > 1) {
|
||||
initials += _.last(nameParts).charAt(0)
|
||||
}
|
||||
return {
|
||||
kind: 'initials',
|
||||
initials
|
||||
}
|
||||
}
|
||||
},
|
||||
isAdmin () {
|
||||
return _.intersection(this.permissions, ['manage:system', 'write:users', 'manage:users', 'write:groups', 'manage:groups', 'manage:navigation', 'manage:theme', 'manage:api']).length > 0
|
||||
},
|
||||
hasNewPagePermission () {
|
||||
return this.hasAdminPermission || _.intersection(this.permissions, ['write:pages']).length > 0
|
||||
},
|
||||
hasAdminPermission: get('page/effectivePermissions@system.manage'),
|
||||
hasWritePagesPermission: get('page/effectivePermissions@pages.write'),
|
||||
hasManagePagesPermission: get('page/effectivePermissions@pages.manage'),
|
||||
hasDeletePagesPermission: get('page/effectivePermissions@pages.delete'),
|
||||
hasReadSourcePermission: get('page/effectivePermissions@source.read'),
|
||||
hasReadHistoryPermission: get('page/effectivePermissions@history.read'),
|
||||
hasAnyPagePermissions () {
|
||||
return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission ||
|
||||
this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.hideSearch || this.dense || this.$vuetify.breakpoint.smAndDown) {
|
||||
this.searchIsShown = false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('pageEdit', () => {
|
||||
this.pageEdit()
|
||||
})
|
||||
this.$root.$on('pageHistory', () => {
|
||||
this.pageHistory()
|
||||
})
|
||||
this.$root.$on('pageSource', () => {
|
||||
this.pageSource()
|
||||
})
|
||||
this.$root.$on('pageMove', () => {
|
||||
this.pageMove()
|
||||
})
|
||||
this.$root.$on('pageConvert', () => {
|
||||
this.pageConvert()
|
||||
})
|
||||
this.$root.$on('pageDuplicate', () => {
|
||||
this.pageDuplicate()
|
||||
})
|
||||
this.$root.$on('pageDelete', () => {
|
||||
this.pageDelete()
|
||||
})
|
||||
this.isDevMode = siteConfig.devMode === true
|
||||
},
|
||||
methods: {
|
||||
searchFocus () {
|
||||
this.searchIsFocused = true
|
||||
},
|
||||
searchBlur () {
|
||||
this.searchIsFocused = false
|
||||
},
|
||||
searchClose () {
|
||||
this.search = ''
|
||||
this.searchBlur()
|
||||
},
|
||||
searchToggle () {
|
||||
this.searchIsShown = !this.searchIsShown
|
||||
if (this.searchIsShown) {
|
||||
_.delay(() => {
|
||||
this.$refs.searchFieldMobile.focus()
|
||||
}, 200)
|
||||
}
|
||||
},
|
||||
searchEnter () {
|
||||
this.$root.$emit('searchEnter', true)
|
||||
},
|
||||
searchMove(dir) {
|
||||
this.$root.$emit('searchMove', dir)
|
||||
},
|
||||
pageNew () {
|
||||
this.newPageModal = true
|
||||
},
|
||||
pageNewCreate ({ path, locale }) {
|
||||
window.location.assign(`/e/${locale}/${path}`)
|
||||
},
|
||||
pageView () {
|
||||
window.location.assign(`/${this.locale}/${this.path}`)
|
||||
},
|
||||
pageEdit () {
|
||||
window.location.assign(`/e/${this.locale}/${this.path}`)
|
||||
},
|
||||
pageHistory () {
|
||||
window.location.assign(`/h/${this.locale}/${this.path}`)
|
||||
},
|
||||
pageSource () {
|
||||
window.location.assign(`/s/${this.locale}/${this.path}`)
|
||||
},
|
||||
pageDuplicate () {
|
||||
const pathParts = this.path.split('/')
|
||||
this.duplicateOpts = {
|
||||
locale: this.locale,
|
||||
path: (pathParts.length > 1) ? _.initial(pathParts).join('/') + `/new-page` : `new-page`,
|
||||
modal: true
|
||||
}
|
||||
},
|
||||
pageDuplicateHandle ({ locale, path }) {
|
||||
window.location.assign(`/e/${locale}/${path}?from=${this.$store.get('page/id')}`)
|
||||
},
|
||||
pageConvert () {
|
||||
this.convertPageModal = true
|
||||
},
|
||||
pageMove () {
|
||||
this.movePageModal = true
|
||||
},
|
||||
async pageMoveRename ({ path, locale }) {
|
||||
this.$store.commit(`loadingStart`, 'page-move')
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: movePageMutation,
|
||||
variables: {
|
||||
id: this.$store.get('page/id'),
|
||||
destinationLocale: locale,
|
||||
destinationPath: path
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.pages.move.responseResult.succeeded', false)) {
|
||||
window.location.replace(`/${locale}/${path}`)
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.pages.move.responseResult.message', this.$t('common:error.unexpected')))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
this.$store.commit(`loadingStop`, 'page-move')
|
||||
}
|
||||
},
|
||||
pageDelete () {
|
||||
this.deletePageModal = true
|
||||
},
|
||||
assets () {
|
||||
// window.location.assign(`/f`)
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'indigo',
|
||||
message: `Coming soon...`,
|
||||
icon: 'ferry'
|
||||
})
|
||||
},
|
||||
async changeLocale (locale) {
|
||||
await this.$i18n.i18next.changeLanguage(locale.code)
|
||||
switch (this.mode) {
|
||||
case 'view':
|
||||
case 'history':
|
||||
window.location.assign(`/${locale.code}/${this.path}`)
|
||||
break
|
||||
}
|
||||
},
|
||||
logout () {
|
||||
window.location.assign('/logout')
|
||||
},
|
||||
goHome () {
|
||||
window.location.assign('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
.nav-header {
|
||||
//z-index: 1000;
|
||||
|
||||
.v-toolbar__extension {
|
||||
padding: 0;
|
||||
|
||||
.v-toolbar__content {
|
||||
padding: 0;
|
||||
}
|
||||
.v-text-field .v-input__prepend-inner {
|
||||
padding: 0 14px 0 5px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.org-logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-inner {
|
||||
.v-toolbar__content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-search-adv {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 12px;
|
||||
border-radius: 4px !important;
|
||||
|
||||
@at-root .v-application--is-rtl & {
|
||||
right: initial;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
position: absolute !important;
|
||||
|
||||
&::before {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-dev {
|
||||
background-color: mc('red', '600');
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: 255px;
|
||||
padding: 5px 15px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
|
||||
.v-icon {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.overline:nth-child(2) {
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navHeaderSearch {
|
||||
&-enter-active, &-leave-active {
|
||||
transition: opacity .25s ease, transform .25s ease;
|
||||
opacity: 1;
|
||||
}
|
||||
&-enter-active {
|
||||
transition-delay: .25s;
|
||||
}
|
||||
&-enter, &-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(.7, .7);
|
||||
}
|
||||
}
|
||||
.navHeaderLoading { // To avoid search bar jumping
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
</style>
|
@ -1,62 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-snackbar.nav-notify(
|
||||
:color='notification.style'
|
||||
top
|
||||
multi-line
|
||||
v-model='notificationState'
|
||||
:timeout='6000'
|
||||
)
|
||||
.text-left
|
||||
v-icon.mr-3(dark) mdi-{{ notification.icon }}
|
||||
span {{ notification.message }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return { }
|
||||
},
|
||||
computed: {
|
||||
notification: get('notification'),
|
||||
notificationState: sync('notification@isActive')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.nav-notify {
|
||||
top: -64px;
|
||||
padding-top: 0;
|
||||
z-index: 999;
|
||||
|
||||
.v-snack__wrapper {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
position: relative;
|
||||
margin-top: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: rgba(255,255,255,.4);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
animation: nav-notify-anim 6s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nav-notify-anim {
|
||||
0% {
|
||||
width: 100%;
|
||||
}
|
||||
100% {
|
||||
width: 0%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,120 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-dialog(
|
||||
v-model='isShown'
|
||||
max-width='550'
|
||||
persistent
|
||||
overlay-color='blue-grey darken-4'
|
||||
overlay-opacity='.7'
|
||||
)
|
||||
v-card
|
||||
.dialog-header.is-short.is-dark
|
||||
v-icon.mr-2(color='white') mdi-lightning-bolt
|
||||
span {{$t('common:page.convert')}}
|
||||
v-card-text.pt-5
|
||||
i18next.body-2(path='common:page.convertTitle', tag='div')
|
||||
span.blue-grey--text.text--darken-2(place='title') {{pageTitle}}
|
||||
v-select.mt-5(
|
||||
:items=`[
|
||||
{ value: 'markdown', text: 'Markdown' },
|
||||
{ value: 'ckeditor', text: 'Visual Editor' },
|
||||
{ value: 'code', text: 'Raw HTML' }
|
||||
]`
|
||||
outlined
|
||||
dense
|
||||
hide-details
|
||||
v-model='newEditor'
|
||||
)
|
||||
.caption.mt-5 {{$t('common:page.convertSubtitle')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='discard', :disabled='loading') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-4(color='grey darken-3', @click='convertPage', :loading='loading').white--text {{$t('common:actions.convert')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { get } from 'vuex-pathify'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
newEditor: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
},
|
||||
pageTitle: get('page/title'),
|
||||
pagePath: get('page/path'),
|
||||
pageLocale: get('page/locale'),
|
||||
pageId: get('page/id'),
|
||||
pageEditor: get('page/editor')
|
||||
},
|
||||
mounted () {
|
||||
this.newEditor = this.pageEditor
|
||||
},
|
||||
methods: {
|
||||
discard() {
|
||||
this.isShown = false
|
||||
},
|
||||
async convertPage() {
|
||||
this.loading = true
|
||||
this.$store.commit(`loadingStart`, 'page-convert')
|
||||
this.$nextTick(async () => {
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation (
|
||||
$id: Int!
|
||||
$editor: String!
|
||||
) {
|
||||
pages {
|
||||
convert(
|
||||
id: $id
|
||||
editor: $editor
|
||||
) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: this.pageId,
|
||||
editor: this.newEditor
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.pages.convert.responseResult.succeeded', false)) {
|
||||
this.isShown = false
|
||||
window.location.assign(`/e/${this.pageLocale}/${this.pagePath}`)
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.pages.convert.responseResult.message', this.$t('common:error.unexpected')))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
}
|
||||
this.$store.commit(`loadingStop`, 'page-convert')
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
@ -1,125 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-dialog(
|
||||
v-model='isShown'
|
||||
max-width='550'
|
||||
persistent
|
||||
overlay-color='red darken-4'
|
||||
overlay-opacity='.7'
|
||||
)
|
||||
v-card
|
||||
.dialog-header.is-short.is-red
|
||||
v-icon.mr-2(color='white') mdi-file-document-box-remove-outline
|
||||
span {{$t('common:page.delete')}}
|
||||
v-card-text.pt-5
|
||||
i18next.body-1(path='common:page.deleteTitle', tag='div')
|
||||
span.red--text.text--darken-2(place='title') {{pageTitle}}
|
||||
.caption {{$t('common:page.deleteSubtitle')}}
|
||||
v-chip.mt-3.ml-0.mr-1(label, color='red lighten-4', small)
|
||||
.caption.red--text.text--darken-2 {{pageLocale.toUpperCase()}}
|
||||
v-chip.mt-3.mx-0(label, color='red lighten-5', small)
|
||||
span.red--text.text--darken-2 /{{pagePath}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='discard', :disabled='loading') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-4(color='red darken-2', @click='deletePage', :loading='loading').white--text {{$t('common:actions.delete')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { get } from 'vuex-pathify'
|
||||
|
||||
import deletePageMutation from 'gql/common/common-pages-mutation-delete.gql'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
},
|
||||
pageTitle: get('page/title'),
|
||||
pagePath: get('page/path'),
|
||||
pageLocale: get('page/locale'),
|
||||
pageId: get('page/id')
|
||||
},
|
||||
watch: {
|
||||
isShown(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
document.body.classList.add('page-deleted-pending')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
discard() {
|
||||
document.body.classList.remove('page-deleted-pending')
|
||||
this.isShown = false
|
||||
},
|
||||
async deletePage() {
|
||||
this.loading = true
|
||||
this.$store.commit(`loadingStart`, 'page-delete')
|
||||
this.$nextTick(async () => {
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: deletePageMutation,
|
||||
variables: {
|
||||
id: this.pageId
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.pages.delete.responseResult.succeeded', false)) {
|
||||
this.isShown = false
|
||||
_.delay(() => {
|
||||
document.body.classList.add('page-deleted')
|
||||
_.delay(() => {
|
||||
window.location.assign('/')
|
||||
}, 1200)
|
||||
}, 400)
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.pages.delete.responseResult.message', this.$t('common:error.unexpected')))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
}
|
||||
this.$store.commit(`loadingStop`, 'page-delete')
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
body.page-deleted-pending {
|
||||
perspective: 50vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
.application {
|
||||
background-color: mc('grey', '900');
|
||||
}
|
||||
.application--wrap {
|
||||
transform-style: preserve-3d;
|
||||
transform: translateZ(-5vw) rotateX(2deg);
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
body.page-deleted {
|
||||
perspective: 50vw;
|
||||
|
||||
.application--wrap {
|
||||
transform-style: preserve-3d;
|
||||
transform: translateZ(-1000vw) rotateX(60deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,329 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-dialog(
|
||||
v-model='isShown'
|
||||
max-width='850px'
|
||||
overlay-color='blue darken-4'
|
||||
overlay-opacity='.7'
|
||||
)
|
||||
v-card.page-selector
|
||||
.dialog-header.is-blue
|
||||
v-icon.mr-3(color='white') mdi-page-next-outline
|
||||
.body-1(v-if='mode === `create`') {{$t('common:pageSelector.createTitle')}}
|
||||
.body-1(v-else-if='mode === `move`') {{$t('common:pageSelector.moveTitle')}}
|
||||
.body-1(v-else-if='mode === `select`') {{$t('common:pageSelector.selectTitle')}}
|
||||
v-spacer
|
||||
v-progress-circular(
|
||||
indeterminate
|
||||
color='white'
|
||||
:size='20'
|
||||
:width='2'
|
||||
v-show='searchLoading'
|
||||
)
|
||||
.d-flex
|
||||
v-flex.grey(xs5, :class='$vuetify.theme.dark ? `darken-4` : `lighten-3`')
|
||||
v-toolbar(color='grey darken-3', dark, dense, flat)
|
||||
.body-2 {{$t('common:pageSelector.virtualFolders')}}
|
||||
v-spacer
|
||||
v-btn(icon, tile, href='https://docs.requarks.io/guide/pages#folders', target='_blank')
|
||||
v-icon mdi-help-box
|
||||
div(style='height:400px;')
|
||||
vue-scroll(:ops='scrollStyle')
|
||||
v-treeview(
|
||||
:key='`pageTree-` + treeViewCacheId'
|
||||
:active.sync='currentNode'
|
||||
:open.sync='openNodes'
|
||||
:items='tree'
|
||||
:load-children='fetchFolders'
|
||||
dense
|
||||
expand-icon='mdi-menu-down-outline'
|
||||
item-id='path'
|
||||
item-text='title'
|
||||
activatable
|
||||
hoverable
|
||||
)
|
||||
template(slot='prepend', slot-scope='{ item, open, leaf }')
|
||||
v-icon mdi-{{ open ? 'folder-open' : 'folder' }}
|
||||
v-flex(xs7)
|
||||
v-toolbar(color='blue darken-2', dark, dense, flat)
|
||||
.body-2 {{$t('common:pageSelector.pages')}}
|
||||
//- v-spacer
|
||||
//- v-btn(icon, tile, disabled): v-icon mdi-content-save-move-outline
|
||||
//- v-btn(icon, tile, disabled): v-icon mdi-trash-can-outline
|
||||
div(v-if='currentPages.length > 0', style='height:400px;')
|
||||
vue-scroll(:ops='scrollStyle')
|
||||
v-list.py-0(dense)
|
||||
v-list-item-group(
|
||||
v-model='currentPage'
|
||||
color='primary'
|
||||
)
|
||||
template(v-for='(page, idx) of currentPages')
|
||||
v-list-item(:key='`page-` + page.id', :value='page')
|
||||
v-list-item-icon: v-icon mdi-text-box
|
||||
v-list-item-title {{page.title}}
|
||||
v-divider(v-if='idx < pages.length - 1')
|
||||
v-alert.animated.fadeIn(
|
||||
v-else
|
||||
text
|
||||
color='orange'
|
||||
prominent
|
||||
icon='mdi-alert'
|
||||
)
|
||||
.body-2 {{$t('common:pageSelector.folderEmptyWarning')}}
|
||||
v-card-actions.grey.pa-2(:class='$vuetify.theme.dark ? `darken-2` : `lighten-1`', v-if='!mustExist')
|
||||
v-select(
|
||||
solo
|
||||
dark
|
||||
flat
|
||||
background-color='grey darken-3-d2'
|
||||
hide-details
|
||||
single-line
|
||||
:items='namespaces'
|
||||
style='flex: 0 0 100px; border-radius: 4px 0 0 4px;'
|
||||
v-model='currentLocale'
|
||||
)
|
||||
v-text-field(
|
||||
ref='pathIpt'
|
||||
solo
|
||||
hide-details
|
||||
prefix='/'
|
||||
v-model='currentPath'
|
||||
flat
|
||||
clearable
|
||||
style='border-radius: 0 4px 4px 0;'
|
||||
)
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='close') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-4(color='primary', @click='open', :disabled='!isValidPath')
|
||||
v-icon(left) mdi-check
|
||||
span {{$t('common:actions.select')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
|
||||
|
||||
/* global siteLangs, siteConfig */
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: 'new-page'
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
openHandler: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
mustExist: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
treeViewCacheId: 0,
|
||||
searchLoading: false,
|
||||
currentLocale: siteConfig.lang,
|
||||
currentFolderPath: '',
|
||||
currentPath: 'new-page',
|
||||
currentPage: null,
|
||||
currentNode: [0],
|
||||
openNodes: [0],
|
||||
tree: [
|
||||
{
|
||||
id: 0,
|
||||
title: '/ (root)',
|
||||
children: []
|
||||
}
|
||||
],
|
||||
pages: [],
|
||||
all: [],
|
||||
namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang],
|
||||
scrollStyle: {
|
||||
vuescroll: {},
|
||||
scrollPanel: {
|
||||
initialScrollX: 0.01, // fix scrollbar not disappearing on load
|
||||
scrollingX: false,
|
||||
speed: 50
|
||||
},
|
||||
rail: {
|
||||
gutterOfEnds: '2px'
|
||||
},
|
||||
bar: {
|
||||
onlyShowBarOnScroll: false,
|
||||
background: '#999',
|
||||
hoverStyle: {
|
||||
background: '#64B5F6'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
},
|
||||
currentPages () {
|
||||
return _.sortBy(_.filter(this.pages, ['parent', _.head(this.currentNode) || 0]), ['title', 'path'])
|
||||
},
|
||||
isValidPath () {
|
||||
if (!this.currentPath) {
|
||||
return false
|
||||
}
|
||||
if (this.mustExist && !this.currentPage) {
|
||||
return false
|
||||
}
|
||||
const firstSection = _.head(this.currentPath.split('/'))
|
||||
if (firstSection.length <= 1) {
|
||||
return false
|
||||
} else if (localeSegmentRegex.test(firstSection)) {
|
||||
return false
|
||||
} else if (
|
||||
_.some(['login', 'logout', 'register', 'verify', 'favicons', 'fonts', 'img', 'js', 'svg'], p => {
|
||||
return p === firstSection
|
||||
})) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isShown (newValue, oldValue) {
|
||||
if (newValue && !oldValue) {
|
||||
this.currentPath = this.path
|
||||
this.currentLocale = this.locale
|
||||
_.delay(() => {
|
||||
this.$refs.pathIpt.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
currentNode (newValue, oldValue) {
|
||||
if (newValue.length < 1) { // force a selection
|
||||
this.$nextTick(() => {
|
||||
this.currentNode = oldValue
|
||||
})
|
||||
} else {
|
||||
const current = _.find(this.all, ['id', newValue[0]])
|
||||
|
||||
if (this.openNodes.indexOf(newValue[0]) < 0) { // auto open and load children
|
||||
if (current) {
|
||||
if (this.openNodes.indexOf(current.parent) < 0) {
|
||||
this.$nextTick(() => {
|
||||
this.openNodes.push(current.parent)
|
||||
})
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.openNodes.push(newValue[0])
|
||||
})
|
||||
}
|
||||
|
||||
this.currentPath = _.compact([_.get(current, 'path', ''), _.last(this.currentPath.split('/'))]).join('/')
|
||||
}
|
||||
},
|
||||
currentPage (newValue, oldValue) {
|
||||
if (!_.isEmpty(newValue)) {
|
||||
this.currentPath = newValue.path
|
||||
}
|
||||
},
|
||||
currentLocale (newValue, oldValue) {
|
||||
this.$nextTick(() => {
|
||||
this.tree = [
|
||||
{
|
||||
id: 0,
|
||||
title: '/ (root)',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
this.currentNode = [0]
|
||||
this.openNodes = [0]
|
||||
this.pages = []
|
||||
this.all = []
|
||||
this.treeViewCacheId += 1
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.isShown = false
|
||||
},
|
||||
open() {
|
||||
const exit = this.openHandler({
|
||||
locale: this.currentLocale,
|
||||
path: this.currentPath,
|
||||
id: (this.mustExist && this.currentPage) ? this.currentPage.pageId : 0
|
||||
})
|
||||
if (exit !== false) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
async fetchFolders (item) {
|
||||
this.searchLoading = true
|
||||
const resp = await this.$apollo.query({
|
||||
query: gql`
|
||||
query ($parent: Int!, $mode: PageTreeMode!, $locale: String!) {
|
||||
pages {
|
||||
tree(parent: $parent, mode: $mode, locale: $locale) {
|
||||
id
|
||||
path
|
||||
title
|
||||
isFolder
|
||||
pageId
|
||||
parent
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'network-only',
|
||||
variables: {
|
||||
parent: item.id,
|
||||
mode: 'ALL',
|
||||
locale: this.currentLocale
|
||||
}
|
||||
})
|
||||
const items = _.get(resp, 'data.pages.tree', [])
|
||||
const itemFolders = _.filter(items, ['isFolder', true]).map(f => ({...f, children: []}))
|
||||
const itemPages = _.filter(items, i => i.pageId > 0)
|
||||
if (itemFolders.length > 0) {
|
||||
item.children = itemFolders
|
||||
} else {
|
||||
item.children = undefined
|
||||
}
|
||||
this.pages = _.unionBy(this.pages, itemPages, 'id')
|
||||
this.all = _.unionBy(this.all, items, 'id')
|
||||
|
||||
this.searchLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
.page-selector {
|
||||
.v-treeview-node__label {
|
||||
font-size: 13px;
|
||||
}
|
||||
.v-treeview-node__content {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -1,79 +0,0 @@
|
||||
<template lang="pug">
|
||||
.password-strength
|
||||
v-progress-linear(
|
||||
:color='passwordStrengthColor'
|
||||
v-model='passwordStrength'
|
||||
height='2'
|
||||
)
|
||||
.caption(v-if='!hideText', :class='passwordStrengthColor + "--text"') {{passwordStrengthText}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import zxcvbn from 'zxcvbn'
|
||||
import _ from 'lodash'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
hideText: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
passwordStrength: 0,
|
||||
passwordStrengthColor: 'grey',
|
||||
passwordStrengthText: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.checkPasswordStrength(newValue)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkPasswordStrength: _.debounce(function (pwd) {
|
||||
if (!pwd || pwd.length < 1) {
|
||||
this.passwordStrength = 0
|
||||
this.passwordStrengthColor = 'grey'
|
||||
this.passwordStrengthText = ''
|
||||
return
|
||||
}
|
||||
const strength = zxcvbn(pwd)
|
||||
this.passwordStrength = _.round((strength.score + 1) / 5 * 100)
|
||||
if (this.passwordStrength <= 20) {
|
||||
this.passwordStrengthColor = 'red'
|
||||
this.passwordStrengthText = this.$t('common:password.veryWeak')
|
||||
} else if (this.passwordStrength <= 40) {
|
||||
this.passwordStrengthColor = 'orange'
|
||||
this.passwordStrengthText = this.$t('common:password.weak')
|
||||
} else if (this.passwordStrength <= 60) {
|
||||
this.passwordStrengthColor = 'teal'
|
||||
this.passwordStrengthText = this.$t('common:password.average')
|
||||
} else if (this.passwordStrength <= 80) {
|
||||
this.passwordStrengthColor = 'green'
|
||||
this.passwordStrengthText = this.$t('common:password.strong')
|
||||
} else {
|
||||
this.passwordStrengthColor = 'green'
|
||||
this.passwordStrengthText = this.$t('common:password.veryStrong')
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.password-strength > .caption {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: calc(100% + 5px);
|
||||
}
|
||||
|
||||
</style>
|
@ -1,246 +0,0 @@
|
||||
<template lang="pug">
|
||||
.search-results(v-if='searchIsFocused || (search && search.length > 1)')
|
||||
.search-results-container
|
||||
.search-results-help(v-if='!search || (search && search.length < 2)')
|
||||
img(src='/_assets-legacy/svg/icon-search-alt.svg')
|
||||
.mt-4 {{$t('common:header.searchHint')}}
|
||||
.search-results-loader(v-else-if='searchIsLoading && (!results || results.length < 1)')
|
||||
orbit-spinner(
|
||||
:animation-duration='1000'
|
||||
:size='100'
|
||||
color='#FFF'
|
||||
)
|
||||
.headline.mt-5 {{$t('common:header.searchLoading')}}
|
||||
.search-results-none(v-else-if='!searchIsLoading && (!results || results.length < 1)')
|
||||
img(src='/_assets-legacy/svg/icon-no-results.svg', alt='No Results')
|
||||
.subheading {{$t('common:header.searchNoResult')}}
|
||||
template(v-if='search && search.length >= 2 && results && results.length > 0')
|
||||
v-subheader.white--text {{$t('common:header.searchResultsCount', { total: response.totalHits })}}
|
||||
v-list.search-results-items.radius-7.py-0(two-line, dense)
|
||||
template(v-for='(item, idx) of results')
|
||||
v-list-item(@click='goToPage(item)', @click.middle="goToPageInNewTab(item)", :key='item.id', :class='idx === cursor ? `highlighted` : ``')
|
||||
v-list-item-avatar(tile)
|
||||
img(src='/_assets-legacy/svg/icon-selective-highlighting.svg')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text='item.title')
|
||||
v-list-item-subtitle.caption(v-text='item.description')
|
||||
.caption.grey--text(v-text='item.path')
|
||||
v-list-item-action
|
||||
v-chip(label, outlined) {{item.locale.toUpperCase()}}
|
||||
v-divider(v-if='idx < results.length - 1')
|
||||
v-pagination.mt-3(
|
||||
v-if='paginationLength > 1'
|
||||
dark
|
||||
v-model='pagination'
|
||||
:length='paginationLength'
|
||||
circle
|
||||
)
|
||||
template(v-if='suggestions && suggestions.length > 0')
|
||||
v-subheader.white--text.mt-3 {{$t('common:header.searchDidYouMean')}}
|
||||
v-list.search-results-suggestions.radius-7(dense, dark)
|
||||
template(v-for='(term, idx) of suggestions')
|
||||
v-list-item(:key='term', @click='setSearchTerm(term)', :class='idx + results.length === cursor ? `highlighted` : ``')
|
||||
v-list-item-avatar
|
||||
v-icon mdi-magnify
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text='term')
|
||||
v-divider(v-if='idx < suggestions.length - 1')
|
||||
.text-xs-center.pt-5(v-if='search && search.length > 1')
|
||||
//- v-btn.mx-2(outlined, color='orange', @click='search = ``', v-if='results.length > 0')
|
||||
//- v-icon(left) mdi-content-save
|
||||
//- span {{$t('common:header.searchCopyLink')}}
|
||||
v-btn.mx-2(outlined, color='pink', @click='search = ``')
|
||||
v-icon(left) mdi-close
|
||||
span {{$t('common:header.searchClose')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { sync } from 'vuex-pathify'
|
||||
import { OrbitSpinner } from 'epic-spinners'
|
||||
|
||||
import searchPagesQuery from 'gql/common/common-pages-query-search.gql'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OrbitSpinner
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cursor: 0,
|
||||
pagination: 1,
|
||||
perPage: 10,
|
||||
response: {
|
||||
results: [],
|
||||
suggestions: [],
|
||||
totalHits: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
search: sync('site/search'),
|
||||
searchIsFocused: sync('site/searchIsFocused'),
|
||||
searchIsLoading: sync('site/searchIsLoading'),
|
||||
searchRestrictLocale: sync('site/searchRestrictLocale'),
|
||||
searchRestrictPath: sync('site/searchRestrictPath'),
|
||||
results() {
|
||||
const currentIndex = (this.pagination - 1) * this.perPage
|
||||
return this.response.results ? _.slice(this.response.results, currentIndex, currentIndex + this.perPage) : []
|
||||
},
|
||||
hits() {
|
||||
return this.response.totalHits ? this.response.totalHits : 0
|
||||
},
|
||||
suggestions() {
|
||||
return this.response.suggestions ? this.response.suggestions : []
|
||||
},
|
||||
paginationLength() {
|
||||
return (this.response.totalHits > 0) ? Math.ceil(this.response.totalHits / this.perPage) : 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search(newValue, oldValue) {
|
||||
this.cursor = 0
|
||||
if (!newValue || (newValue && newValue.length < 2)) {
|
||||
this.searchIsLoading = false
|
||||
} else {
|
||||
this.searchIsLoading = true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$root.$on('searchMove', (dir) => {
|
||||
this.cursor += ((dir === 'up') ? -1 : 1)
|
||||
if (this.cursor < -1) {
|
||||
this.cursor = -1
|
||||
} else if (this.cursor > this.results.length + this.suggestions.length - 1) {
|
||||
this.cursor = this.results.length + this.suggestions.length - 1
|
||||
}
|
||||
})
|
||||
this.$root.$on('searchEnter', () => {
|
||||
if (!this.results) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.cursor >= 0 && this.cursor < this.results.length) {
|
||||
this.goToPage(_.nth(this.results, this.cursor))
|
||||
} else if (this.cursor >= 0) {
|
||||
this.setSearchTerm(_.nth(this.suggestions, this.cursor - this.results.length))
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
setSearchTerm(term) {
|
||||
this.search = term
|
||||
},
|
||||
goToPage(item) {
|
||||
window.location.assign(`/${item.locale}/${item.path}`)
|
||||
},
|
||||
goToPageInNewTab(item) {
|
||||
window.open(`/${item.locale}/${item.path}`, '_blank')
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
response: {
|
||||
query: searchPagesQuery,
|
||||
variables() {
|
||||
return {
|
||||
query: this.search
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
debounce: 300,
|
||||
throttle: 1000,
|
||||
skip() {
|
||||
return !this.search || this.search.length < 2
|
||||
},
|
||||
update: (data) => _.get(data, 'pages.search', {}),
|
||||
watchLoading (isLoading) {
|
||||
this.searchIsLoading = isLoading
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.search-results {
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
height: calc(100% - 64px);
|
||||
background-color: rgba(0,0,0,.9);
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
animation: searchResultsReveal .6s ease;
|
||||
|
||||
@media #{map-get($display-breakpoints, 'sm-and-down')} {
|
||||
top: 112px;
|
||||
}
|
||||
|
||||
&-container {
|
||||
margin: 12px auto;
|
||||
width: 90vw;
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
&-help {
|
||||
text-align: center;
|
||||
padding: 32px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
color: #FFF;
|
||||
|
||||
img {
|
||||
width: 104px;
|
||||
}
|
||||
}
|
||||
|
||||
&-loader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 32px 0;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
&-none {
|
||||
color: #FFF;
|
||||
|
||||
img {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
&-items {
|
||||
text-align: left;
|
||||
|
||||
.highlighted {
|
||||
background: #FFF linear-gradient(to bottom, #FFF, mc('orange', '100'));
|
||||
|
||||
@at-root .theme--dark & {
|
||||
background: mc('grey', '900') linear-gradient(to bottom, mc('orange', '900'), darken(mc('orange', '900'), 15%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-suggestions {
|
||||
.highlighted {
|
||||
background: transparent linear-gradient(to bottom, mc('blue', '500'), mc('blue', '700'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes searchResultsReveal {
|
||||
0% {
|
||||
background-color: rgba(0,0,0,0);
|
||||
padding-top: 32px;
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(0,0,0,.9);
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,106 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-list(nav, dense)
|
||||
v-list-item(@click='', ref='copyUrlButton')
|
||||
v-icon(color='grey', small) mdi-content-copy
|
||||
v-list-item-title.px-3 Copy URL
|
||||
v-list-item(:href='`mailto:?subject=` + encodeURIComponent(title) + `&body=` + encodeURIComponent(url) + `%0D%0A%0D%0A` + encodeURIComponent(description)')
|
||||
v-icon(color='grey', small) mdi-email-outline
|
||||
v-list-item-title.px-3 Email
|
||||
v-list-item(@click='openSocialPop(`https://www.facebook.com/sharer/sharer.php?u=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title) + `&description=` + encodeURIComponent(description))')
|
||||
v-icon(color='grey', small) mdi-facebook
|
||||
v-list-item-title.px-3 Facebook
|
||||
v-list-item(@click='openSocialPop(`https://www.linkedin.com/shareArticle?mini=true&url=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title) + `&summary=` + encodeURIComponent(description))')
|
||||
v-icon(color='grey', small) mdi-linkedin
|
||||
v-list-item-title.px-3 LinkedIn
|
||||
v-list-item(@click='openSocialPop(`https://www.reddit.com/submit?url=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title))')
|
||||
v-icon(color='grey', small) mdi-reddit
|
||||
v-list-item-title.px-3 Reddit
|
||||
v-list-item(@click='openSocialPop(`https://t.me/share/url?url=` + encodeURIComponent(url) + `&text=` + encodeURIComponent(title))')
|
||||
v-icon(color='grey', small) mdi-telegram
|
||||
v-list-item-title.px-3 Telegram
|
||||
v-list-item(@click='openSocialPop(`https://twitter.com/intent/tweet?url=` + encodeURIComponent(url) + `&text=` + encodeURIComponent(title))')
|
||||
v-icon(color='grey', small) mdi-twitter
|
||||
v-list-item-title.px-3 Twitter
|
||||
v-list-item(:href='`viber://forward?text=` + encodeURIComponent(url) + ` ` + encodeURIComponent(description)')
|
||||
v-icon(color='grey', small) mdi-phone-in-talk
|
||||
v-list-item-title.px-3 Viber
|
||||
v-list-item(@click='openSocialPop(`http://service.weibo.com/share/share.php?url=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title))')
|
||||
v-icon(color='grey', small) mdi-sina-weibo
|
||||
v-list-item-title.px-3 Weibo
|
||||
v-list-item(@click='openSocialPop(`https://api.whatsapp.com/send?text=` + encodeURIComponent(title) + `%0D%0A` + encodeURIComponent(url))')
|
||||
v-icon(color='grey', small) mdi-whatsapp
|
||||
v-list-item-title.px-3 Whatsapp
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClipboardJS from 'clipboard'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: window.location.url
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Untitled Page'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
width: 626,
|
||||
height: 436,
|
||||
left: 0,
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSocialPop (url) {
|
||||
const popupWindow = window.open(
|
||||
url,
|
||||
'sharer',
|
||||
`status=no,height=${this.height},width=${this.width},resizable=yes,left=${this.left},top=${this.top},screenX=${this.left},screenY=${this.top},toolbar=no,menubar=no,scrollbars=no,location=no,directories=no`
|
||||
)
|
||||
|
||||
popupWindow.focus()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const clip = new ClipboardJS(this.$refs.copyUrlButton.$el, {
|
||||
text: () => { return this.url }
|
||||
})
|
||||
|
||||
clip.on('success', () => {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'success',
|
||||
message: `URL copied successfully`,
|
||||
icon: 'content-copy'
|
||||
})
|
||||
})
|
||||
clip.on('error', () => {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: `Failed to copy to clipboard`,
|
||||
icon: 'alert'
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Center the popup on dual screens
|
||||
* http://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen/32261263
|
||||
*/
|
||||
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left
|
||||
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top
|
||||
|
||||
const width = window.innerWidth ? window.innerWidth : (document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width)
|
||||
const height = window.innerHeight ? window.innerHeight : (document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height)
|
||||
|
||||
this.left = ((width / 2) - (this.width / 2)) + dualScreenLeft
|
||||
this.top = ((height / 2) - (this.height / 2)) + dualScreenTop
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,136 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-dialog(
|
||||
v-model='dialogOpen'
|
||||
max-width='650'
|
||||
)
|
||||
v-card
|
||||
.dialog-header
|
||||
span {{$t('common:user.search')}}
|
||||
v-spacer
|
||||
v-progress-circular(
|
||||
indeterminate
|
||||
color='white'
|
||||
:size='20'
|
||||
:width='2'
|
||||
v-show='searchLoading'
|
||||
)
|
||||
v-card-text.pt-5
|
||||
v-text-field(
|
||||
outlined
|
||||
:label='$t(`common:user.searchPlaceholder`)'
|
||||
v-model='search'
|
||||
prepend-inner-icon='mdi-account-search-outline'
|
||||
color='primary'
|
||||
ref='searchIpt'
|
||||
hide-details
|
||||
)
|
||||
v-list.grey.mt-3.py-0.radius-7(
|
||||
:class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-3`'
|
||||
two-line
|
||||
dense
|
||||
)
|
||||
template(v-for='(usr, idx) in items')
|
||||
v-list-item(:key='usr.id', @click='setUser(usr)')
|
||||
v-list-item-avatar(size='40', color='primary')
|
||||
span.body-1.white--text {{usr.name | initials}}
|
||||
v-list-item-content
|
||||
v-list-item-title.body-2 {{usr.name}}
|
||||
v-list-item-subtitle {{usr.email}}
|
||||
v-list-item-action
|
||||
v-icon(color='primary') mdi-arrow-right
|
||||
v-divider.my-0(v-if='idx < items.length - 1')
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(
|
||||
text
|
||||
@click='close'
|
||||
:disabled='loading'
|
||||
) {{$t('common:actions.cancel')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
initials(val) {
|
||||
return val.split(' ').map(v => v.substring(0, 1)).join('')
|
||||
}
|
||||
},
|
||||
props: {
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
searchLoading: false,
|
||||
search: '',
|
||||
items: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogOpen: {
|
||||
get() { return this.value },
|
||||
set(value) { this.$emit('input', value) }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue, oldValue) {
|
||||
if (newValue && !oldValue) {
|
||||
this.search = ''
|
||||
this.selectedItems = null
|
||||
_.delay(() => { this.$refs.searchIpt.focus() }, 100)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
setUser(usr) {
|
||||
this.$emit('select', usr)
|
||||
this.close()
|
||||
},
|
||||
searchFilter(item, queryText, itemText) {
|
||||
return _.includes(_.toLower(item.email), _.toLower(queryText)) || _.includes(_.toLower(item.name), _.toLower(queryText))
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
items: {
|
||||
query: gql`
|
||||
query ($query: String!) {
|
||||
users {
|
||||
search(query:$query) {
|
||||
id
|
||||
name
|
||||
email
|
||||
providerKey
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return {
|
||||
query: this.search
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
skip() {
|
||||
return !this.search || this.search.length < 2
|
||||
},
|
||||
update: (data) => data.users.search,
|
||||
watchLoading (isLoading) {
|
||||
this.searchLoading = isLoading
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,10 +0,0 @@
|
||||
<template lang='pug'>
|
||||
div
|
||||
v-divider.my-0
|
||||
v-card-actions(:class='$vuetify.theme.dark ? "grey darken-4-l5" : "grey lighten-4"')
|
||||
slot
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default { }
|
||||
</script>
|
@ -1,64 +0,0 @@
|
||||
<template lang='pug'>
|
||||
.v-card-info(:class='`is-` + color')
|
||||
v-card-text.d-flex.align-center(:class='colors.cls')
|
||||
v-icon(:color='colors.icon', left) {{icon}}
|
||||
slot
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: 'blue'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'mdi-information-outline'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
switch (this.color) {
|
||||
case 'blue':
|
||||
return {
|
||||
cls: this.$vuetify.theme.dark ? 'grey darken-4-l5 blue--text text--lighten-4' : 'blue lighten-5 blue--text text--darken-3',
|
||||
icon: 'blue lighten-3'
|
||||
}
|
||||
case 'red':
|
||||
return {
|
||||
cls: this.$vuetify.theme.dark ? 'grey darken-4-l5 red--text text--lighten-4' : 'red lighten-5 red--text text--darken-2',
|
||||
icon: 'red lighten-3'
|
||||
}
|
||||
default:
|
||||
return {
|
||||
cls: this.$vuetify.theme.dark ? 'grey darken-4-l5' : 'grey lighten-4',
|
||||
icon: 'grey darken-2'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.v-card-info {
|
||||
border-bottom: 1px solid #EEE;
|
||||
|
||||
&.is-blue {
|
||||
border-bottom-color: mc('blue', '100');
|
||||
|
||||
@at-root .theme--dark & {
|
||||
border-bottom-color: rgba(mc('blue', '100'), .3);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-red {
|
||||
border-bottom-color: mc('red', '100');
|
||||
|
||||
@at-root .theme--dark & {
|
||||
border-bottom-color: rgba(mc('red', '100'), .3);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,568 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-app.editor(:dark='$vuetify.theme.dark')
|
||||
nav-header(dense)
|
||||
template(slot='mid')
|
||||
v-text-field.editor-title-input(
|
||||
dark
|
||||
solo
|
||||
flat
|
||||
v-model='currentPageTitle'
|
||||
hide-details
|
||||
background-color='black'
|
||||
dense
|
||||
full-width
|
||||
)
|
||||
template(slot='actions')
|
||||
v-btn.mr-3.animated.fadeIn(color='amber', outlined, small, v-if='isConflict', @click='openConflict')
|
||||
.overline.amber--text.mr-3 Conflict
|
||||
status-indicator(intermediary, pulse)
|
||||
v-btn.animated.fadeInDown(
|
||||
text
|
||||
color='green'
|
||||
@click.exact='save'
|
||||
@click.ctrl.exact='saveAndClose'
|
||||
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
|
||||
)
|
||||
v-icon(color='green', :left='$vuetify.breakpoint.lgAndUp') mdi-check
|
||||
span.grey--text(v-if='$vuetify.breakpoint.lgAndUp && mode !== `create` && !isDirty') {{ $t('editor:save.saved') }}
|
||||
span.white--text(v-else-if='$vuetify.breakpoint.lgAndUp') {{ mode === 'create' ? $t('common:actions.create') : $t('common:actions.save') }}
|
||||
v-btn.animated.fadeInDown.wait-p1s(
|
||||
text
|
||||
color='blue'
|
||||
@click='openPropsModal'
|
||||
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown, "mx-0": !welcomeMode, "ml-0": welcomeMode }'
|
||||
)
|
||||
v-icon(color='blue', :left='$vuetify.breakpoint.lgAndUp') mdi-tag-text-outline
|
||||
span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('common:actions.page') }}
|
||||
v-btn.animated.fadeInDown.wait-p2s(
|
||||
v-if='!welcomeMode'
|
||||
text
|
||||
color='red'
|
||||
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
|
||||
@click='exit'
|
||||
)
|
||||
v-icon(color='red', :left='$vuetify.breakpoint.lgAndUp') mdi-close
|
||||
span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('common:actions.close') }}
|
||||
v-divider.ml-3(vertical)
|
||||
v-main
|
||||
component(:is='currentEditor', :save='save')
|
||||
editor-modal-properties(v-model='dialogProps')
|
||||
editor-modal-editorselect(v-model='dialogEditorSelector')
|
||||
editor-modal-unsaved(v-model='dialogUnsaved', @discard='exitGo')
|
||||
component(:is='activeModal')
|
||||
|
||||
loader(v-model='dialogProgress', :title='$t(`editor:save.processing`)', :subtitle='$t(`editor:save.pleaseWait`)')
|
||||
notify
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
import { AtomSpinner } from 'epic-spinners'
|
||||
import { Base64 } from 'js-base64'
|
||||
import { StatusIndicator } from 'vue-status-indicator'
|
||||
|
||||
import editorStore from '../store/editor'
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
WIKI.$store.registerModule('editor', editorStore)
|
||||
|
||||
export default {
|
||||
i18nOptions: { namespaces: 'editor' },
|
||||
components: {
|
||||
AtomSpinner,
|
||||
StatusIndicator,
|
||||
editorApi: () => import(/* webpackChunkName: "editor-api", webpackMode: "lazy" */ './editor/editor-api.vue'),
|
||||
editorCode: () => import(/* webpackChunkName: "editor-code", webpackMode: "lazy" */ './editor/editor-code.vue'),
|
||||
editorCkeditor: () => import(/* webpackChunkName: "editor-ckeditor", webpackMode: "lazy" */ './editor/editor-ckeditor.vue'),
|
||||
editorMarkdown: () => import(/* webpackChunkName: "editor-markdown", webpackMode: "lazy" */ './editor/editor-markdown.vue'),
|
||||
editorRedirect: () => import(/* webpackChunkName: "editor-redirect", webpackMode: "lazy" */ './editor/editor-redirect.vue'),
|
||||
editorModalEditorselect: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-editorselect.vue'),
|
||||
editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.vue'),
|
||||
editorModalUnsaved: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-unsaved.vue'),
|
||||
editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-media.vue'),
|
||||
editorModalBlocks: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-blocks.vue'),
|
||||
editorModalConflict: () => import(/* webpackChunkName: "editor-conflict", webpackMode: "lazy" */ './editor/editor-modal-conflict.vue'),
|
||||
editorModalDrawio: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-drawio.vue')
|
||||
},
|
||||
props: {
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: 'home'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Untitled Page'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
isPublished: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
scriptCss: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
publishStartDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
publishEndDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
scriptJs: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
initEditor: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
initMode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
initContent: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
pageId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
checkoutDate: {
|
||||
type: String,
|
||||
default: new Date().toISOString()
|
||||
},
|
||||
effectivePermissions: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSaving: false,
|
||||
isConflict: false,
|
||||
dialogProps: false,
|
||||
dialogProgress: false,
|
||||
dialogEditorSelector: false,
|
||||
dialogUnsaved: false,
|
||||
exitConfirmed: false,
|
||||
initContentParsed: '',
|
||||
savedState: {
|
||||
description: '',
|
||||
isPublished: false,
|
||||
publishEndDate: '',
|
||||
publishStartDate: '',
|
||||
tags: '',
|
||||
title: '',
|
||||
css: '',
|
||||
js: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentEditor: sync('editor/editor'),
|
||||
activeModal: sync('editor/activeModal'),
|
||||
mode: get('editor/mode'),
|
||||
welcomeMode() { return this.mode === `create` && this.path === `home` },
|
||||
currentPageTitle: sync('page/title'),
|
||||
checkoutDateActive: sync('editor/checkoutDateActive'),
|
||||
currentStyling: get('page/scriptCss'),
|
||||
isDirty () {
|
||||
return _.some([
|
||||
this.initContentParsed !== this.$store.get('editor/content'),
|
||||
this.locale !== this.$store.get('page/locale'),
|
||||
this.path !== this.$store.get('page/path'),
|
||||
this.savedState.title !== this.$store.get('page/title'),
|
||||
this.savedState.description !== this.$store.get('page/description'),
|
||||
this.savedState.tags !== this.$store.get('page/tags'),
|
||||
this.savedState.isPublished !== this.$store.get('page/isPublished'),
|
||||
this.savedState.publishStartDate !== this.$store.get('page/publishStartDate'),
|
||||
this.savedState.publishEndDate !== this.$store.get('page/publishEndDate'),
|
||||
this.savedState.css !== this.$store.get('page/scriptCss'),
|
||||
this.savedState.js !== this.$store.get('page/scriptJs')
|
||||
], Boolean)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentEditor(newValue, oldValue) {
|
||||
if (newValue !== '' && this.mode === 'create') {
|
||||
_.delay(() => {
|
||||
this.dialogProps = true
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
currentStyling(newValue) {
|
||||
this.injectCustomCss(newValue)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
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)
|
||||
this.$store.set('page/title', this.title)
|
||||
this.$store.set('page/scriptCss', this.scriptCss)
|
||||
this.$store.set('page/scriptJs', this.scriptJs)
|
||||
|
||||
this.$store.set('page/mode', 'edit')
|
||||
|
||||
this.setCurrentSavedState()
|
||||
|
||||
this.checkoutDateActive = this.checkoutDate
|
||||
|
||||
if (this.effectivePermissions) {
|
||||
this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString()))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.set('editor/mode', this.initMode || 'create')
|
||||
|
||||
this.initContentParsed = this.initContent ? Base64.decode(this.initContent) : ''
|
||||
this.$store.set('editor/content', this.initContentParsed)
|
||||
if (this.mode === 'create' && !this.initEditor) {
|
||||
_.delay(() => {
|
||||
this.dialogEditorSelector = true
|
||||
}, 500)
|
||||
} else {
|
||||
this.currentEditor = `editor${_.startCase(this.initEditor || 'markdown')}`
|
||||
}
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
if (!this.exitConfirmed && this.initContentParsed !== this.$store.get('editor/content')) {
|
||||
return this.$t('editor:unsavedWarning')
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
this.$root.$on('resetEditorConflict', () => {
|
||||
this.isConflict = false
|
||||
})
|
||||
|
||||
// this.$store.set('editor/mode', 'edit')
|
||||
// this.currentEditor = `editorApi`
|
||||
},
|
||||
methods: {
|
||||
openPropsModal(name) {
|
||||
this.dialogProps = true
|
||||
},
|
||||
showProgressDialog(textKey) {
|
||||
this.dialogProgress = true
|
||||
},
|
||||
hideProgressDialog() {
|
||||
this.dialogProgress = false
|
||||
},
|
||||
openConflict() {
|
||||
this.$root.$emit('saveConflict')
|
||||
},
|
||||
async save({ rethrow = false, overwrite = false } = {}) {
|
||||
this.showProgressDialog('saving')
|
||||
this.isSaving = true
|
||||
|
||||
const saveTimeoutHandle = setTimeout(() => {
|
||||
throw new Error('Save operation timed out.')
|
||||
}, 30000)
|
||||
|
||||
try {
|
||||
if (this.$store.get('editor/mode') === 'create') {
|
||||
// --------------------------------------------
|
||||
// -> CREATE PAGE
|
||||
// --------------------------------------------
|
||||
|
||||
let resp = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation (
|
||||
$content: String!
|
||||
$description: String!
|
||||
$editor: String!
|
||||
$publishState: PagePublishState!
|
||||
$locale: String!
|
||||
$path: String!
|
||||
$publishEndDate: Date
|
||||
$publishStartDate: Date
|
||||
$scriptCss: String
|
||||
$scriptJsLoad: String
|
||||
$siteId: UUID!
|
||||
$tags: [String!]
|
||||
$title: String!
|
||||
) {
|
||||
createPage(
|
||||
content: $content
|
||||
description: $description
|
||||
editor: $editor
|
||||
publishState: $publishState
|
||||
locale: $locale
|
||||
path: $path
|
||||
publishEndDate: $publishEndDate
|
||||
publishStartDate: $publishStartDate
|
||||
scriptCss: $scriptCss
|
||||
scriptJsLoad: $scriptJsLoad
|
||||
siteId: $siteId
|
||||
tags: $tags
|
||||
title: $title
|
||||
) {
|
||||
operation {
|
||||
succeeded
|
||||
message
|
||||
}
|
||||
page {
|
||||
id
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
content: this.$store.get('editor/content'),
|
||||
description: this.$store.get('page/description'),
|
||||
editor: this.$store.get('editor/editorKey'),
|
||||
locale: this.$store.get('page/locale'),
|
||||
publishState: this.$store.get('page/isPublished') ? 'published' : 'draft',
|
||||
path: this.$store.get('page/path'),
|
||||
publishEndDate: this.$store.get('page/publishEndDate') || '',
|
||||
publishStartDate: this.$store.get('page/publishStartDate') || '',
|
||||
scriptCss: this.$store.get('page/scriptCss'),
|
||||
scriptJsLoad: this.$store.get('page/scriptJs'),
|
||||
siteId: this.$store.get('site/id'),
|
||||
tags: this.$store.get('page/tags'),
|
||||
title: this.$store.get('page/title')
|
||||
}
|
||||
})
|
||||
resp = resp?.data?.createPage || {}
|
||||
if (resp?.operation?.succeeded) {
|
||||
this.checkoutDateActive = resp?.page?.updatedAt ?? this.checkoutDateActive
|
||||
this.isConflict = false
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:save.createSuccess'),
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
this.$store.set('editor/id', resp?.page?.id)
|
||||
this.$store.set('editor/mode', 'update')
|
||||
this.exitConfirmed = true
|
||||
window.location.assign(`/${this.$store.get('page/locale')}/${this.$store.get('page/path')}`)
|
||||
} else {
|
||||
throw new Error(resp?.operation?.message)
|
||||
}
|
||||
} else {
|
||||
// --------------------------------------------
|
||||
// -> UPDATE EXISTING PAGE
|
||||
// --------------------------------------------
|
||||
|
||||
// const conflictResp = await this.$apollo.query({
|
||||
// query: gql`
|
||||
// query ($id: Int!, $checkoutDate: Date!) {
|
||||
// pages {
|
||||
// checkConflicts(id: $id, checkoutDate: $checkoutDate)
|
||||
// }
|
||||
// }
|
||||
// `,
|
||||
// fetchPolicy: 'network-only',
|
||||
// variables: {
|
||||
// id: this.pageId,
|
||||
// checkoutDate: this.checkoutDateActive
|
||||
// }
|
||||
// })
|
||||
// if (_.get(conflictResp, 'data.pages.checkConflicts', false)) {
|
||||
// this.$root.$emit('saveConflict')
|
||||
// throw new Error(this.$t('editor:conflict.warning'))
|
||||
// }
|
||||
|
||||
let resp = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation (
|
||||
$id: UUID!
|
||||
$patch: PageUpdateInput!
|
||||
) {
|
||||
updatePage(
|
||||
id: $id
|
||||
patch: $patch
|
||||
) {
|
||||
operation {
|
||||
succeeded
|
||||
message
|
||||
}
|
||||
page {
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: this.$store.get('page/id'),
|
||||
patch: {
|
||||
content: this.$store.get('editor/content'),
|
||||
description: this.$store.get('page/description'),
|
||||
locale: this.$store.get('page/locale'),
|
||||
publishState: this.$store.get('page/isPublished') ? 'published' : 'draft',
|
||||
path: this.$store.get('page/path'),
|
||||
publishEndDate: this.$store.get('page/publishEndDate') || '',
|
||||
publishStartDate: this.$store.get('page/publishStartDate') || '',
|
||||
scriptCss: this.$store.get('page/scriptCss'),
|
||||
scriptJsLoad: this.$store.get('page/scriptJs'),
|
||||
tags: this.$store.get('page/tags'),
|
||||
title: this.$store.get('page/title')
|
||||
}
|
||||
}
|
||||
})
|
||||
resp = _.get(resp, 'data.updatePage', {})
|
||||
if (_.get(resp, 'operation.succeeded')) {
|
||||
this.checkoutDateActive = _.get(resp, 'page.updatedAt', this.checkoutDateActive)
|
||||
this.isConflict = false
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:save.updateSuccess'),
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
if (this.locale !== this.$store.get('page/locale') || this.path !== this.$store.get('page/path')) {
|
||||
_.delay(() => {
|
||||
window.location.replace(`/e/${this.$store.get('page/locale')}/${this.$store.get('page/path')}`)
|
||||
}, 1000)
|
||||
}
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'operation.message'))
|
||||
}
|
||||
}
|
||||
|
||||
this.initContentParsed = this.$store.get('editor/content')
|
||||
this.setCurrentSavedState()
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
message: err.message,
|
||||
style: 'error',
|
||||
icon: 'warning'
|
||||
})
|
||||
if (rethrow === true) {
|
||||
clearTimeout(saveTimeoutHandle)
|
||||
this.isSaving = false
|
||||
this.hideProgressDialog()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
clearTimeout(saveTimeoutHandle)
|
||||
this.isSaving = false
|
||||
this.hideProgressDialog()
|
||||
},
|
||||
async saveAndClose() {
|
||||
try {
|
||||
if (this.$store.get('editor/mode') === 'create') {
|
||||
await this.save()
|
||||
} else {
|
||||
await this.save({ rethrow: true })
|
||||
await this.exit()
|
||||
}
|
||||
} catch (err) {
|
||||
// Error is already handled
|
||||
}
|
||||
},
|
||||
async exit() {
|
||||
if (this.isDirty) {
|
||||
this.dialogUnsaved = true
|
||||
} else {
|
||||
this.exitGo()
|
||||
}
|
||||
},
|
||||
exitGo() {
|
||||
this.$store.commit(`loadingStart`, 'editor-close')
|
||||
this.currentEditor = ''
|
||||
this.exitConfirmed = true
|
||||
_.delay(() => {
|
||||
if (this.$store.get('editor/mode') === 'create') {
|
||||
window.location.assign(`/`)
|
||||
} else {
|
||||
window.location.assign(`/${this.$store.get('page/locale')}/${this.$store.get('page/path')}`)
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
setCurrentSavedState () {
|
||||
this.savedState = {
|
||||
description: this.$store.get('page/description'),
|
||||
isPublished: this.$store.get('page/isPublished'),
|
||||
publishEndDate: this.$store.get('page/publishEndDate') || '',
|
||||
publishStartDate: this.$store.get('page/publishStartDate') || '',
|
||||
tags: this.$store.get('page/tags'),
|
||||
title: this.$store.get('page/title'),
|
||||
css: this.$store.get('page/scriptCss'),
|
||||
js: this.$store.get('page/scriptJs')
|
||||
}
|
||||
},
|
||||
injectCustomCss: _.debounce(css => {
|
||||
const oldStyl = document.querySelector('#editor-script-css')
|
||||
if (oldStyl) {
|
||||
document.head.removeChild(oldStyl)
|
||||
}
|
||||
if (!_.isEmpty(css)) {
|
||||
const styl = document.createElement('style')
|
||||
styl.type = 'text/css'
|
||||
styl.id = 'editor-script-css'
|
||||
document.head.appendChild(styl)
|
||||
styl.appendChild(document.createTextNode(css))
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
// apollo: {
|
||||
// isConflict: {
|
||||
// query: gql`
|
||||
// query ($id: Int!, $checkoutDate: Date!) {
|
||||
// pages {
|
||||
// checkConflicts(id: $id, checkoutDate: $checkoutDate)
|
||||
// }
|
||||
// }
|
||||
// `,
|
||||
// fetchPolicy: 'network-only',
|
||||
// pollInterval: 5000,
|
||||
// variables () {
|
||||
// return {
|
||||
// id: this.pageId,
|
||||
// checkoutDate: this.checkoutDateActive
|
||||
// }
|
||||
// },
|
||||
// update: (data) => _.cloneDeep(data.pages.checkConflicts),
|
||||
// skip () {
|
||||
// return this.mode === 'create' || this.isSaving || !this.isDirty
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
.editor {
|
||||
background-color: mc('grey', '900') !important;
|
||||
min-height: 100vh;
|
||||
|
||||
.application--wrap {
|
||||
background-color: mc('grey', '900');
|
||||
}
|
||||
|
||||
&-title-input input {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.atom-spinner.is-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
</style>
|
@ -1,9 +0,0 @@
|
||||
<template lang="pug">
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
@ -1,129 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-dialog(
|
||||
v-model='isShown'
|
||||
max-width='700'
|
||||
)
|
||||
v-card
|
||||
.dialog-header.is-short.is-indigo
|
||||
v-icon.mr-2(color='white') mdi-alert
|
||||
span {{$t('editor:conflict.title')}}
|
||||
v-card-text.pt-4
|
||||
i18next.body-2(tag='div', path='editor:conflict.infoGeneric')
|
||||
strong(place='authorName') {{latest.authorName}}
|
||||
span(place='date', :title='$options.filters.moment(latest.updatedAt, `LLL`)') {{ latest.updatedAt | moment('from') }}.
|
||||
v-btn.mt-2(outlined, color='indigo', small, :href='`/` + latest.locale + `/` + latest.path', target='_blank')
|
||||
v-icon(left) mdi-open-in-new
|
||||
span {{$t('editor:conflict.viewLatestVersion')}}
|
||||
.body-2.mt-5: strong {{$t('editor:conflict.whatToDo')}}
|
||||
.body-2.mt-1 #[v-icon(color='indigo') mdi-alpha-l-box] {{$t('editor:conflict.whatToDoLocal')}}
|
||||
.body-2.mt-1 #[v-icon(color='indigo') mdi-alpha-r-box] {{$t('editor:conflict.whatToDoRemote')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='close') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-4(color='indigo', @click='useLocal', dark, :title='$t(`editor:conflict.useLocalHint`)')
|
||||
v-icon(left) mdi-alpha-l-box
|
||||
span {{$t('editor:conflict.useLocal')}}
|
||||
v-dialog(
|
||||
v-model='isRemoteConfirmDiagShown'
|
||||
width='500'
|
||||
)
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.ml-3(color='indigo', dark, v-on='on', :title='$t(`editor:conflict.useRemoteHint`)')
|
||||
v-icon(left) mdi-alpha-r-box
|
||||
span {{$t('editor:conflict.useRemote')}}
|
||||
v-card
|
||||
.dialog-header.is-short.is-indigo
|
||||
v-icon.mr-3(color='white') mdi-alpha-r-box
|
||||
span {{$t('editor:conflict.overwrite.title')}}
|
||||
v-card-text.pa-4
|
||||
i18next.body-2(tag='div', path='editor:conflict.overwrite.description')
|
||||
strong(place='refEditsLost') {{$t('editor:conflict.overwrite.editsLost')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(outlined, color='indigo', @click='isRemoteConfirmDiagShown = false')
|
||||
v-icon(left) mdi-close
|
||||
span {{$t('common:actions.cancel')}}
|
||||
v-btn(@click='useRemote', color='indigo', dark)
|
||||
v-icon(left) mdi-check
|
||||
span {{$t('common:actions.confirm')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
latest: {
|
||||
updatedAt: '',
|
||||
authorName: '',
|
||||
content: '',
|
||||
locale: '',
|
||||
path: ''
|
||||
},
|
||||
isRemoteConfirmDiagShown: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.isShown = false
|
||||
},
|
||||
useLocal () {
|
||||
this.$store.set('editor/checkoutDateActive', this.latest.updatedAt)
|
||||
this.$root.$emit('resetEditorConflict')
|
||||
this.close()
|
||||
},
|
||||
useRemote () {
|
||||
this.$store.set('editor/checkoutDateActive', this.latest.updatedAt)
|
||||
this.$store.set('editor/content', this.latest.content)
|
||||
this.$root.$emit('overwriteEditorContent')
|
||||
this.$root.$emit('resetEditorConflict')
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
let resp = await this.$apollo.query({
|
||||
query: gql`
|
||||
query ($id: Int!) {
|
||||
pages {
|
||||
conflictLatest(id: $id) {
|
||||
authorName
|
||||
locale
|
||||
path
|
||||
content
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'network-only',
|
||||
variables: {
|
||||
id: this.$store.get('page/id')
|
||||
}
|
||||
})
|
||||
resp = _.get(resp, 'data.pages.conflictLatest', false)
|
||||
|
||||
if (!resp) {
|
||||
return this.$store.commit('showNotification', {
|
||||
message: 'Failed to fetch latest version.',
|
||||
style: 'warning',
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
this.latest = resp
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,147 +0,0 @@
|
||||
// Test if potential opening or closing delimieter
|
||||
// Assumes that there is a "$" at state.src[pos]
|
||||
function isValidDelim (state, pos) {
|
||||
let prevChar
|
||||
let nextChar
|
||||
let max = state.posMax
|
||||
let canOpen = true
|
||||
let canClose = true
|
||||
|
||||
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1
|
||||
nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1
|
||||
|
||||
// Check non-whitespace conditions for opening and closing, and
|
||||
// check that closing delimeter isn't followed by a number
|
||||
if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ ||
|
||||
(nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) {
|
||||
canClose = false
|
||||
}
|
||||
if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) {
|
||||
canOpen = false
|
||||
}
|
||||
|
||||
return {
|
||||
canOpen: canOpen,
|
||||
canClose: canClose
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
katexInline (state, silent) {
|
||||
let start, match, token, res, pos
|
||||
|
||||
if (state.src[state.pos] !== '$') { return false }
|
||||
|
||||
res = isValidDelim(state, state.pos)
|
||||
if (!res.canOpen) {
|
||||
if (!silent) { state.pending += '$' }
|
||||
state.pos += 1
|
||||
return true
|
||||
}
|
||||
|
||||
// First check for and bypass all properly escaped delimieters
|
||||
// This loop will assume that the first leading backtick can not
|
||||
// be the first character in state.src, which is known since
|
||||
// we have found an opening delimieter already.
|
||||
start = state.pos + 1
|
||||
match = start
|
||||
while ((match = state.src.indexOf('$', match)) !== -1) {
|
||||
// Found potential $, look for escapes, pos will point to
|
||||
// first non escape when complete
|
||||
pos = match - 1
|
||||
while (state.src[pos] === '\\') { pos -= 1 }
|
||||
|
||||
// Even number of escapes, potential closing delimiter found
|
||||
if (((match - pos) % 2) === 1) { break }
|
||||
match += 1
|
||||
}
|
||||
|
||||
// No closing delimter found. Consume $ and continue.
|
||||
if (match === -1) {
|
||||
if (!silent) { state.pending += '$' }
|
||||
state.pos = start
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if we have empty content, ie: $$. Do not parse.
|
||||
if (match - start === 0) {
|
||||
if (!silent) { state.pending += '$$' }
|
||||
state.pos = start + 1
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for valid closing delimiter
|
||||
res = isValidDelim(state, match)
|
||||
if (!res.canClose) {
|
||||
if (!silent) { state.pending += '$' }
|
||||
state.pos = start
|
||||
return true
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
token = state.push('katex_inline', 'math', 0)
|
||||
token.markup = '$'
|
||||
token.content = state.src
|
||||
// Extract the math part without the $
|
||||
.slice(start, match)
|
||||
// Escape the curly braces since they will be interpreted as
|
||||
// attributes by markdown-it-attrs (the "curly_attributes"
|
||||
// core rule)
|
||||
.replaceAll("{", "{{")
|
||||
.replaceAll("}", "}}")
|
||||
}
|
||||
|
||||
state.pos = match + 1
|
||||
return true
|
||||
},
|
||||
|
||||
katexBlock (state, start, end, silent) {
|
||||
let firstLine; let lastLine; let next; let lastPos; let found = false; let token
|
||||
let pos = state.bMarks[start] + state.tShift[start]
|
||||
let max = state.eMarks[start]
|
||||
|
||||
if (pos + 2 > max) { return false }
|
||||
if (state.src.slice(pos, pos + 2) !== '$$') { return false }
|
||||
|
||||
pos += 2
|
||||
firstLine = state.src.slice(pos, max)
|
||||
|
||||
if (silent) { return true }
|
||||
if (firstLine.trim().slice(-2) === '$$') {
|
||||
// Single line expression
|
||||
firstLine = firstLine.trim().slice(0, -2)
|
||||
found = true
|
||||
}
|
||||
|
||||
for (next = start; !found;) {
|
||||
next++
|
||||
|
||||
if (next >= end) { break }
|
||||
|
||||
pos = state.bMarks[next] + state.tShift[next]
|
||||
max = state.eMarks[next]
|
||||
|
||||
if (pos < max && state.tShift[next] < state.blkIndent) {
|
||||
// non-empty line with negative indent should stop the list:
|
||||
break
|
||||
}
|
||||
|
||||
if (state.src.slice(pos, max).trim().slice(-2) === '$$') {
|
||||
lastPos = state.src.slice(0, max).lastIndexOf('$$')
|
||||
lastLine = state.src.slice(pos, lastPos)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
state.line = next + 1
|
||||
|
||||
token = state.push('katex_block', 'math', 0)
|
||||
token.block = true
|
||||
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
|
||||
state.getLines(start + 1, next, state.tShift[start], true) +
|
||||
(lastLine && lastLine.trim() ? lastLine : '')
|
||||
token.map = [ start, state.line ]
|
||||
token.markup = '$$'
|
||||
return true
|
||||
}
|
||||
}
|
@ -1,433 +0,0 @@
|
||||
<template lang='pug'>
|
||||
.editor-api
|
||||
.editor-api-main
|
||||
v-list.editor-api-sidebar.radius-0(nav, :class='$vuetify.theme.dark ? `grey darken-4` : `primary`', dark)
|
||||
v-list-item-group(v-model='tab')
|
||||
v-list-item.animated.fadeInLeft(value='info')
|
||||
v-list-item-icon: v-icon mdi-book-information-variant
|
||||
v-list-item-title Info
|
||||
v-list-item.mt-3.animated.fadeInLeft.wait-p2s(value='servers')
|
||||
v-list-item-icon: v-icon mdi-server
|
||||
v-list-item-title Servers
|
||||
v-list-item.mt-3.animated.fadeInLeft.wait-p3s(value='endpoints')
|
||||
v-list-item-icon: v-icon mdi-code-braces
|
||||
v-list-item-title Endpoints
|
||||
v-list-item.mt-3.animated.fadeInLeft.wait-p4s(value='models')
|
||||
v-list-item-icon: v-icon mdi-buffer
|
||||
v-list-item-title Models
|
||||
v-list-item.mt-3.animated.fadeInLeft.wait-p5s(value='auth')
|
||||
v-list-item-icon: v-icon mdi-lock
|
||||
v-list-item-title Authentication
|
||||
.editor-api-editor
|
||||
template(v-if='tab === `info`')
|
||||
v-container.px-2.pt-1(fluid)
|
||||
v-row(dense)
|
||||
v-col(cols='12')
|
||||
.pa-3
|
||||
.subtitle-2 API General Information
|
||||
.caption.grey--text.text--darken-1 Global metadata about the API
|
||||
v-col(cols='12', lg='6')
|
||||
v-card.pt-2
|
||||
v-card-text
|
||||
v-text-field(
|
||||
label='Title'
|
||||
outlined
|
||||
hint='Required - Title of the API'
|
||||
persistent-hint
|
||||
v-model='info.title'
|
||||
)
|
||||
v-divider.mt-2.mb-4
|
||||
v-text-field(
|
||||
label='Version'
|
||||
outlined
|
||||
hint='Required - Semantic versioning like 1.0.0 or an arbitrary string like 0.99-beta.'
|
||||
persistent-hint
|
||||
v-model='info.version'
|
||||
)
|
||||
v-divider.mt-2.mb-4
|
||||
v-textarea(
|
||||
label='Description'
|
||||
outlined
|
||||
hint='Optional - Markdown formatting is supported.'
|
||||
persistent-hint
|
||||
v-model='info.description'
|
||||
)
|
||||
v-col(cols='12', lg='6')
|
||||
v-card.pt-2
|
||||
v-card-text
|
||||
v-list(nav, two-line)
|
||||
v-list-item-group(v-model='kind', mandatory, color='primary')
|
||||
v-list-item(value='rest')
|
||||
v-list-item-avatar
|
||||
img(src='/_assets-legacy/svg/icon-transaction-list.svg', alt='REST')
|
||||
v-list-item-content
|
||||
v-list-item-title REST API
|
||||
v-list-item-subtitle Classic REST Endpoints
|
||||
v-list-item-avatar
|
||||
v-icon(:color='kind === `rest` ? `primary` : `grey lighten-3`') mdi-check-circle
|
||||
v-list-item(value='graphql', disabled)
|
||||
v-list-item-avatar
|
||||
img(src='/_assets-legacy/svg/icon-graphql.svg', alt='GraphQL')
|
||||
v-list-item-content
|
||||
v-list-item-title GraphQL
|
||||
v-list-item-subtitle.grey--text.text--lighten-1 Schema-based API
|
||||
v-list-item-action
|
||||
//- v-icon(:color='kind === `graphql` ? `primary` : `grey lighten-3`') mdi-check-circle
|
||||
v-chip(label, small) Coming soon
|
||||
template(v-else-if='tab === `servers`')
|
||||
v-container.px-2.pt-1(fluid)
|
||||
v-row(dense)
|
||||
v-col(cols='12')
|
||||
.pa-3
|
||||
.d-flex.align-center.justify-space-between
|
||||
div
|
||||
.subtitle-2 List of servers / load balancers where this API reside
|
||||
.caption.grey--text.text--darken-1 Enter all environments, e.g. Integration, QA, Pre-production, Production, etc.
|
||||
v-btn(color='primary', large, @click='addServer')
|
||||
v-icon(left) mdi-plus
|
||||
span Add Server
|
||||
v-col(cols='12', lg='6', v-for='srv of servers', :key='srv.id')
|
||||
v-card.pt-1
|
||||
v-card-text
|
||||
.d-flex
|
||||
.d-flex.flex-column.justify-space-between
|
||||
v-menu(offset-y, min-width='200')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(text, x-large, style='min-width: 0;', v-on='on')
|
||||
v-icon(large, :color='iconColor(srv.icon)') {{iconKey(srv.icon)}}
|
||||
v-list(nav, dense)
|
||||
v-list-item-group(v-model='srv.icon', mandatory)
|
||||
v-list-item(:value='srvKey', v-for='(srv, srvKey) in serverTypes', :key='srvKey')
|
||||
v-list-item-icon: v-icon(large, :color='srv.color', v-text='srv.icon')
|
||||
v-list-item-content: v-list-item-title(v-text='srv.title')
|
||||
v-btn.mb-2(depressed, small, @click='removeServer(srv.id)')
|
||||
v-icon(left) mdi-close
|
||||
span Delete
|
||||
v-divider.ml-5(vertical)
|
||||
.pl-5(style='flex: 1 1 100%;')
|
||||
v-text-field(
|
||||
label='Environment / Server Name'
|
||||
outlined
|
||||
hint='Required - Name of the environment (e.g. QA, Production)'
|
||||
persistent-hint
|
||||
v-model='srv.name'
|
||||
)
|
||||
v-text-field.mt-4(
|
||||
label='URL'
|
||||
outlined
|
||||
hint='Required - URL of the environment (e.g. https://api.example.com/v1)'
|
||||
persistent-hint
|
||||
v-model='srv.url'
|
||||
)
|
||||
|
||||
template(v-else-if='tab === `endpoints`')
|
||||
v-container.px-2.pt-1(fluid)
|
||||
v-row(dense)
|
||||
v-col(cols='12')
|
||||
.pa-3
|
||||
.d-flex.align-center.justify-space-between
|
||||
div
|
||||
.subtitle-2 List of endpoints
|
||||
.caption.grey--text.text--darken-1 Groups of REST endpoints (GET, POST, PUT, DELETE).
|
||||
v-btn(color='primary', large, @click='addGroup')
|
||||
v-icon(left) mdi-plus
|
||||
span Add Group
|
||||
v-col(cols='12', v-for='grp of endpointGroups', :key='grp.id')
|
||||
v-card(color='grey darken-2')
|
||||
v-card-text
|
||||
v-toolbar(color='grey darken-2', flat, height='86')
|
||||
v-text-field.mr-1(
|
||||
flat
|
||||
dark
|
||||
label='Group Name'
|
||||
solo
|
||||
hint='Group Name'
|
||||
persistent-hint
|
||||
v-model='grp.name'
|
||||
)
|
||||
v-text-field.mx-1(
|
||||
flat
|
||||
dark
|
||||
label='Group Description'
|
||||
solo
|
||||
hint='Group Description'
|
||||
persistent-hint
|
||||
v-model='grp.description'
|
||||
)
|
||||
v-divider.mx-3(vertical, dark)
|
||||
v-btn.mx-1.align-self-start(color='grey lighten-2', @click='addEndpoint(grp)', dark, text, height='48')
|
||||
v-icon(left) mdi-trash-can
|
||||
span Delete
|
||||
v-divider.mx-3(vertical, dark)
|
||||
v-btn.ml-1.align-self-start(color='pink', @click='addEndpoint(grp)', dark, depressed, height='48')
|
||||
v-icon(left) mdi-plus
|
||||
span Add Endpoint
|
||||
v-container.pa-0.mt-2(fluid)
|
||||
v-row(dense)
|
||||
v-col(cols='12', v-for='ept of grp.endpoints', :key='ept.id')
|
||||
v-card.pt-1
|
||||
v-card-text
|
||||
.d-flex
|
||||
.d-flex.flex-column
|
||||
v-menu(offset-y, min-width='140')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.subtitle-1(depressed, large, dark, style='min-width: 140px;', height='48', v-on='on', :color='methodColor(ept.method)')
|
||||
strong {{ept.method}}
|
||||
v-list(nav, dense)
|
||||
v-list-item-group(v-model='ept.method', mandatory)
|
||||
v-list-item(:value='mtd.key', v-for='mtd of endpointMethods', :key='mtd.key')
|
||||
v-list-item-content
|
||||
v-chip.text-center(label, :color='mtd.color', dark) {{mtd.key}}
|
||||
v-btn.mt-2(v-if='!ept.expanded', small, @click='ept.expanded = true', color='pink', outlined)
|
||||
v-icon(left) mdi-arrow-down-box
|
||||
span Expand
|
||||
v-btn.mt-2(v-else, small, @click='ept.expanded = false', color='pink', outlined)
|
||||
v-icon(left) mdi-arrow-up-box
|
||||
span Collapse
|
||||
template(v-if='ept.expanded')
|
||||
v-spacer
|
||||
v-btn.my-2(depressed, small, @click='removeEndpoint(grp, ept.id)')
|
||||
v-icon(left) mdi-close
|
||||
span Delete
|
||||
v-divider.ml-5(vertical)
|
||||
.pl-5(style='flex: 1 1 100%;')
|
||||
.d-flex
|
||||
v-text-field.mr-2(
|
||||
label='Path'
|
||||
outlined
|
||||
hint='Required - Path to the endpoint (e.g. /planets/{planetId})'
|
||||
persistent-hint
|
||||
v-model='ept.path'
|
||||
)
|
||||
v-text-field.ml-2(
|
||||
label='Summary'
|
||||
outlined
|
||||
hint='Required - A short summary of the endpoint (a few words).'
|
||||
persistent-hint
|
||||
v-model='ept.summary'
|
||||
)
|
||||
template(v-if='ept.expanded')
|
||||
v-text-field.mt-3(
|
||||
label='Description'
|
||||
outlined
|
||||
v-model='ept.description'
|
||||
)
|
||||
|
||||
v-system-bar.editor-api-sysbar(dark, status, color='grey darken-3')
|
||||
.caption.editor-api-sysbar-locale {{locale.toUpperCase()}}
|
||||
.caption.px-3 /{{path}}
|
||||
template(v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-spacer
|
||||
.caption API Docs
|
||||
v-spacer
|
||||
.caption OpenAPI 3.0
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tab: `endpoints`,
|
||||
kind: 'rest',
|
||||
kinds: [
|
||||
{ text: 'REST', value: 'rest' },
|
||||
{ text: 'GraphQL', value: 'graphql' }
|
||||
],
|
||||
info: {
|
||||
title: '',
|
||||
version: '1.0.0',
|
||||
description: ''
|
||||
},
|
||||
servers: [
|
||||
{ name: 'Production', url: 'https://api.example.com/v1', icon: 'server', id: '123456' }
|
||||
],
|
||||
serverTypes: {
|
||||
aws: {
|
||||
color: 'orange',
|
||||
icon: 'mdi-aws',
|
||||
title: 'AWS'
|
||||
},
|
||||
azure: {
|
||||
color: 'blue darken-2',
|
||||
icon: 'mdi-azure',
|
||||
title: 'Azure'
|
||||
},
|
||||
digitalocean: {
|
||||
color: 'blue',
|
||||
icon: 'mdi-digital-ocean',
|
||||
title: 'DigitalOcean'
|
||||
},
|
||||
docker: {
|
||||
color: 'blue',
|
||||
icon: 'mdi-docker',
|
||||
title: 'Docker'
|
||||
},
|
||||
google: {
|
||||
color: 'red',
|
||||
icon: 'mdi-google',
|
||||
title: 'Google'
|
||||
},
|
||||
kubernetes: {
|
||||
color: 'blue darken-2',
|
||||
icon: 'mdi-kubernetes',
|
||||
title: 'Kubernetes'
|
||||
},
|
||||
linux: {
|
||||
color: 'grey darken-3',
|
||||
icon: 'mdi-linux',
|
||||
title: 'Linux'
|
||||
},
|
||||
mac: {
|
||||
color: 'grey darken-2',
|
||||
icon: 'mdi-apple',
|
||||
title: 'Mac'
|
||||
},
|
||||
server: {
|
||||
color: 'grey',
|
||||
icon: 'mdi-server',
|
||||
title: 'Server'
|
||||
},
|
||||
windows: {
|
||||
color: 'blue darken-2',
|
||||
icon: 'mdi-windows',
|
||||
title: 'Windows'
|
||||
}
|
||||
},
|
||||
endpointGroups: [
|
||||
{
|
||||
id: '345678',
|
||||
name: '',
|
||||
description: '',
|
||||
endpoints: [
|
||||
{ method: 'GET', path: '/pet', expanded: false, id: '234567' }
|
||||
]
|
||||
}
|
||||
],
|
||||
endpointMethods: [
|
||||
{ key: 'GET', color: 'blue' },
|
||||
{ key: 'POST', color: 'green' },
|
||||
{ key: 'PUT', color: 'orange' },
|
||||
{ key: 'PATCH', color: 'cyan' },
|
||||
{ key: 'DELETE', color: 'red' },
|
||||
{ key: 'HEAD', color: 'deep-purple' },
|
||||
{ key: 'OPTIONS', color: 'blue-grey' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$vuetify.breakpoint.smAndDown
|
||||
},
|
||||
locale: get('page/locale'),
|
||||
path: get('page/path'),
|
||||
mode: get('editor/mode'),
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
iconColor (val) {
|
||||
return _.get(this.serverTypes, `${val}.color`, 'white')
|
||||
},
|
||||
iconKey (val) {
|
||||
return _.get(this.serverTypes, `${val}.icon`, 'mdi-server')
|
||||
},
|
||||
methodColor (val) {
|
||||
return _.get(_.find(this.endpointMethods, ['key', val]), 'color', 'grey')
|
||||
},
|
||||
addServer () {
|
||||
this.servers.push({
|
||||
id: uuid(),
|
||||
name: 'Production',
|
||||
url: 'https://api.example.com/v1',
|
||||
icon: 'server'
|
||||
})
|
||||
},
|
||||
removeServer (id) {
|
||||
this.servers = _.reject(this.servers, ['id', id])
|
||||
},
|
||||
addGroup () {
|
||||
this.endpointGroups.push({
|
||||
id: uuid(),
|
||||
name: '',
|
||||
description: '',
|
||||
endpoints: []
|
||||
})
|
||||
},
|
||||
addEndpoint (grp) {
|
||||
grp.endpoints.push({
|
||||
id: uuid(),
|
||||
method: 'GET',
|
||||
path: '/pet',
|
||||
expanded: false
|
||||
})
|
||||
},
|
||||
removeEndpoint (grp, eptId) {
|
||||
grp.endpoints = _.reject(grp.endpoints, ['id', eptId])
|
||||
},
|
||||
toggleModal(key) {
|
||||
this.activeModal = (this.activeModal === key) ? '' : key
|
||||
this.helpShown = false
|
||||
},
|
||||
closeAllModal() {
|
||||
this.activeModal = ''
|
||||
this.helpShown = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.set('editor/editorKey', 'api')
|
||||
|
||||
if (this.mode === 'create') {
|
||||
this.$store.set('editor/content', '<h1>Title</h1>\n\n<p>Some text here</p>')
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('editorInsert')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
$editor-height: calc(100vh - 64px - 24px);
|
||||
$editor-height-mobile: calc(100vh - 56px - 16px);
|
||||
|
||||
.editor-api {
|
||||
&-main {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-editor {
|
||||
background-color: darken(mc('grey', '100'), 4.5%);
|
||||
flex: 1 1 50%;
|
||||
display: block;
|
||||
height: $editor-height;
|
||||
position: relative;
|
||||
|
||||
@at-root .theme--dark & {
|
||||
background-color: darken(mc('grey', '900'), 4.5%);
|
||||
}
|
||||
}
|
||||
|
||||
&-sidebar {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
&-sysbar {
|
||||
padding-left: 0 !important;
|
||||
|
||||
&-locale {
|
||||
background-color: rgba(255,255,255,.25);
|
||||
display:inline-flex;
|
||||
padding: 0 12px;
|
||||
height: 24px;
|
||||
width: 63px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@ -1,253 +0,0 @@
|
||||
<template lang='pug'>
|
||||
.editor-ckeditor
|
||||
div(ref='toolbarContainer')
|
||||
div.contents(ref='editor')
|
||||
v-system-bar.editor-ckeditor-sysbar(dark, status, color='grey darken-3')
|
||||
.caption.editor-ckeditor-sysbar-locale {{locale.toUpperCase()}}
|
||||
.caption.px-3 /{{path}}
|
||||
template(v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-spacer
|
||||
.caption Visual Editor
|
||||
v-spacer
|
||||
.caption {{$t('editor:ckeditor.stats', { chars: stats.characters, words: stats.words })}}
|
||||
editor-conflict(v-model='isConflict', v-if='isConflict')
|
||||
page-selector(mode='select', v-model='insertLinkDialog', :open-handler='insertLinkHandler', :path='path', :locale='locale')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
import DecoupledEditor from '@requarks/ckeditor5'
|
||||
// import DecoupledEditor from '../../../../wiki-ckeditor5/build/ckeditor'
|
||||
import EditorConflict from './ckeditor/conflict.vue'
|
||||
import { html as beautify } from 'js-beautify/js/lib/beautifier.min.js'
|
||||
|
||||
/* global siteLangs */
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorConflict
|
||||
},
|
||||
props: {
|
||||
save: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
stats: {
|
||||
characters: 0,
|
||||
words: 0
|
||||
},
|
||||
content: '',
|
||||
isConflict: false,
|
||||
insertLinkDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$vuetify.breakpoint.smAndDown
|
||||
},
|
||||
locale: get('page/locale'),
|
||||
path: get('page/path'),
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
insertLink () {
|
||||
this.insertLinkDialog = true
|
||||
},
|
||||
insertLinkHandler ({ locale, path }) {
|
||||
this.editor.execute('link', siteLangs.length > 0 ? `/${locale}/${path}` : `/${path}`)
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.$store.set('editor/editorKey', 'ckeditor')
|
||||
|
||||
this.editor = await DecoupledEditor.create(this.$refs.editor, {
|
||||
language: this.locale,
|
||||
placeholder: 'Type the page content here',
|
||||
disableNativeSpellChecker: false,
|
||||
// TODO: Mention autocomplete
|
||||
//
|
||||
// mention: {
|
||||
// feeds: [
|
||||
// {
|
||||
// marker: '@',
|
||||
// feed: [ '@Barney', '@Lily', '@Marshall', '@Robin', '@Ted' ],
|
||||
// minimumCharacters: 1
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
wordCount: {
|
||||
onUpdate: stats => {
|
||||
this.stats = {
|
||||
characters: stats.characters,
|
||||
words: stats.words
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
this.$refs.toolbarContainer.appendChild(this.editor.ui.view.toolbar.element)
|
||||
|
||||
if (this.mode !== 'create') {
|
||||
this.editor.setData(this.$store.get('editor/content'))
|
||||
}
|
||||
|
||||
this.editor.model.document.on('change:data', _.debounce(evt => {
|
||||
this.$store.set('editor/content', beautify(this.editor.getData(), { indent_size: 2, end_with_newline: true }))
|
||||
}, 300))
|
||||
|
||||
this.$root.$on('editorInsert', opts => {
|
||||
switch (opts.kind) {
|
||||
case 'IMAGE':
|
||||
this.editor.execute('imageInsert', {
|
||||
source: opts.path
|
||||
})
|
||||
break
|
||||
case 'BINARY':
|
||||
this.editor.execute('link', opts.path, {
|
||||
linkIsDownloadable: true
|
||||
})
|
||||
break
|
||||
case 'DIAGRAM':
|
||||
this.editor.execute('imageInsert', {
|
||||
source: `data:image/svg+xml;base64,${opts.text}`
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
this.$root.$on('editorLinkToPage', opts => {
|
||||
this.insertLink()
|
||||
})
|
||||
|
||||
// Handle save conflict
|
||||
this.$root.$on('saveConflict', () => {
|
||||
this.isConflict = true
|
||||
})
|
||||
this.$root.$on('overwriteEditorContent', () => {
|
||||
this.editor.setData(this.$store.get('editor/content'))
|
||||
})
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.editor) {
|
||||
this.editor.destroy()
|
||||
this.editor = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
$editor-height: calc(100vh - 64px - 24px);
|
||||
$editor-height-mobile: calc(100vh - 56px - 16px);
|
||||
|
||||
.editor-ckeditor {
|
||||
background-color: mc('grey', '200');
|
||||
flex: 1 1 50%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
height: $editor-height;
|
||||
max-height: $editor-height;
|
||||
position: relative;
|
||||
|
||||
@at-root .theme--dark & {
|
||||
background-color: mc('grey', '900');
|
||||
}
|
||||
|
||||
@include until($tablet) {
|
||||
height: $editor-height-mobile;
|
||||
max-height: $editor-height-mobile;
|
||||
}
|
||||
|
||||
&-sysbar {
|
||||
padding-left: 0;
|
||||
|
||||
&-locale {
|
||||
background-color: rgba(255,255,255,.25);
|
||||
display:inline-flex;
|
||||
padding: 0 12px;
|
||||
height: 24px;
|
||||
width: 63px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.contents {
|
||||
table {
|
||||
margin: inherit;
|
||||
}
|
||||
pre > code {
|
||||
background-color: unset;
|
||||
color: unset;
|
||||
padding: .15em;
|
||||
}
|
||||
}
|
||||
|
||||
.ck.ck-toolbar {
|
||||
border: none;
|
||||
justify-content: center;
|
||||
background-color: mc('grey', '300');
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.ck.ck-toolbar__items {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
> .ck-editor__editable {
|
||||
background-color: mc('grey', '100');
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 0 5px hsla(0, 0, 0, .1);
|
||||
margin: 1rem auto 0;
|
||||
width: calc(100vw - 256px - 16vw);
|
||||
min-height: calc(100vh - 64px - 24px - 1rem - 40px);
|
||||
border-radius: 5px;
|
||||
|
||||
@at-root .theme--dark & {
|
||||
background-color: #303030;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
@include until($widescreen) {
|
||||
width: calc(100vw - 2rem);
|
||||
margin: 1rem 1rem 0 1rem;
|
||||
min-height: calc(100vh - 64px - 24px - 1rem - 40px);
|
||||
}
|
||||
|
||||
@include until($tablet) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
min-height: calc(100vh - 56px - 24px - 76px);
|
||||
}
|
||||
|
||||
&.ck.ck-editor__editable:not(.ck-editor__nested-editable).ck-focused {
|
||||
border-color: #FFF;
|
||||
box-shadow: 0 0 10px rgba(mc('blue', '700'), .25);
|
||||
|
||||
@at-root .theme--dark & {
|
||||
border-color: #444;
|
||||
border-bottom: none;
|
||||
box-shadow: 0 0 10px rgba(#000, .25);
|
||||
}
|
||||
}
|
||||
|
||||
&.ck .ck-editor__nested-editable.ck-editor__nested-editable_focused,
|
||||
&.ck .ck-editor__nested-editable:focus,
|
||||
.ck-widget.table td.ck-editor__nested-editable.ck-editor__nested-editable_focused,
|
||||
.ck-widget.table th.ck-editor__nested-editable.ck-editor__nested-editable_focused {
|
||||
background-color: mc('grey', '100');
|
||||
|
||||
@at-root .theme--dark & {
|
||||
background-color: mc('grey', '900');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,452 +0,0 @@
|
||||
<template lang='pug'>
|
||||
.editor-code
|
||||
.editor-code-main
|
||||
.editor-code-sidebar
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.animated.fadeInLeft(icon, tile, v-on='on', dark, disabled).mx-0
|
||||
v-icon mdi-link-plus
|
||||
span {{$t('editor:markup.insertLink')}}
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mt-3.animated.fadeInLeft.wait-p1s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalMedia`)').mx-0
|
||||
v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') mdi-folder-multiple-image
|
||||
span {{$t('editor:markup.insertAssets')}}
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mt-3.animated.fadeInLeft.wait-p2s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalBlocks`)', disabled).mx-0
|
||||
v-icon(:color='activeModal === `editorModalBlocks` ? `teal` : ``') mdi-view-dashboard-outline
|
||||
span {{$t('editor:markup.insertBlock')}}
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mt-3.animated.fadeInLeft.wait-p3s(icon, tile, v-on='on', dark, disabled).mx-0
|
||||
v-icon mdi-code-braces
|
||||
span {{$t('editor:markup.insertCodeBlock')}}
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mt-3.animated.fadeInLeft.wait-p4s(icon, tile, v-on='on', dark, disabled).mx-0
|
||||
v-icon mdi-library-video
|
||||
span {{$t('editor:markup.insertVideoAudio')}}
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mt-3.animated.fadeInLeft.wait-p5s(icon, tile, v-on='on', dark, disabled).mx-0
|
||||
v-icon mdi-chart-multiline
|
||||
span {{$t('editor:markup.insertDiagram')}}
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mt-3.animated.fadeInLeft.wait-p6s(icon, tile, v-on='on', dark, disabled).mx-0
|
||||
v-icon mdi-function-variant
|
||||
span {{$t('editor:markup.insertMathExpression')}}
|
||||
template(v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-spacer
|
||||
v-tooltip(right, color='teal')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mt-3.animated.fadeInLeft.wait-p8s(icon, tile, v-on='on', dark, @click='toggleFullscreen').mx-0
|
||||
v-icon mdi-arrow-expand-all
|
||||
span {{$t('editor:markup.distractionFreeMode')}}
|
||||
.editor-code-editor
|
||||
textarea(ref='cm')
|
||||
v-system-bar.editor-code-sysbar(dark, status, color='grey darken-3')
|
||||
.caption.editor-code-sysbar-locale {{locale.toUpperCase()}}
|
||||
.caption.px-3 /{{path}}
|
||||
template(v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-spacer
|
||||
.caption Code
|
||||
v-spacer
|
||||
.caption Ln {{cursorPos.line + 1}}, Col {{cursorPos.ch + 1}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
|
||||
// ========================================
|
||||
// IMPORTS
|
||||
// ========================================
|
||||
|
||||
// Code Mirror
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
|
||||
// Language
|
||||
import 'codemirror/mode/htmlmixed/htmlmixed.js'
|
||||
|
||||
// Addons
|
||||
import 'codemirror/addon/selection/active-line.js'
|
||||
import 'codemirror/addon/display/fullscreen.js'
|
||||
import 'codemirror/addon/display/fullscreen.css'
|
||||
import 'codemirror/addon/selection/mark-selection.js'
|
||||
import 'codemirror/addon/search/searchcursor.js'
|
||||
|
||||
// ========================================
|
||||
// INIT
|
||||
// ========================================
|
||||
|
||||
// Platform detection
|
||||
// const CtrlKey = /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl'
|
||||
|
||||
// ========================================
|
||||
// Vue Component
|
||||
// ========================================
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cm: null,
|
||||
cursorPos: { ch: 0, line: 1 }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$vuetify.breakpoint.smAndDown
|
||||
},
|
||||
locale: get('page/locale'),
|
||||
path: get('page/path'),
|
||||
mode: get('editor/mode'),
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
toggleModal(key) {
|
||||
this.activeModal = (this.activeModal === key) ? '' : key
|
||||
this.helpShown = false
|
||||
},
|
||||
closeAllModal() {
|
||||
this.activeModal = ''
|
||||
this.helpShown = false
|
||||
},
|
||||
/**
|
||||
* Insert content at cursor
|
||||
*/
|
||||
insertAtCursor({ content }) {
|
||||
const cursor = this.cm.doc.getCursor('head')
|
||||
this.cm.doc.replaceRange(content, cursor)
|
||||
},
|
||||
/**
|
||||
* Insert content after current line
|
||||
*/
|
||||
insertAfter({ content, newLine }) {
|
||||
const curLine = this.cm.doc.getCursor('to').line
|
||||
const lineLength = this.cm.doc.getLine(curLine).length
|
||||
this.cm.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 })
|
||||
},
|
||||
/**
|
||||
* Insert content before current line
|
||||
*/
|
||||
insertBeforeEachLine({ content, after }) {
|
||||
let lines = []
|
||||
if (!this.cm.doc.somethingSelected()) {
|
||||
lines.push(this.cm.doc.getCursor('head').line)
|
||||
} else {
|
||||
lines = _.flatten(this.cm.doc.listSelections().map(sl => {
|
||||
const range = Math.abs(sl.anchor.line - sl.head.line) + 1
|
||||
const lowestLine = (sl.anchor.line > sl.head.line) ? sl.head.line : sl.anchor.line
|
||||
return _.times(range, l => l + lowestLine)
|
||||
}))
|
||||
}
|
||||
lines.forEach(ln => {
|
||||
let lineContent = this.cm.doc.getLine(ln)
|
||||
const lineLength = lineContent.length
|
||||
if (_.startsWith(lineContent, content)) {
|
||||
lineContent = lineContent.substring(content.length)
|
||||
}
|
||||
|
||||
this.cm.doc.replaceRange(content + lineContent, { line: ln, ch: 0 }, { line: ln, ch: lineLength })
|
||||
})
|
||||
if (after) {
|
||||
const lastLine = _.last(lines)
|
||||
this.cm.doc.replaceRange(`\n${after}\n`, { line: lastLine, ch: this.cm.doc.getLine(lastLine).length + 1 })
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update cursor state
|
||||
*/
|
||||
positionSync(cm) {
|
||||
this.cursorPos = cm.getCursor('head')
|
||||
},
|
||||
toggleFullscreen () {
|
||||
this.cm.setOption('fullScreen', true)
|
||||
},
|
||||
refresh() {
|
||||
this.$nextTick(() => {
|
||||
this.cm.refresh()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.set('editor/editorKey', 'code')
|
||||
|
||||
if (this.mode === 'create') {
|
||||
this.$store.set('editor/content', '<h1>Title</h1>\n\n<p>Some text here</p>')
|
||||
}
|
||||
|
||||
// Initialize CodeMirror
|
||||
|
||||
this.cm = CodeMirror.fromTextArea(this.$refs.cm, {
|
||||
tabSize: 2,
|
||||
mode: 'text/html',
|
||||
theme: 'wikijs-dark',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
line: true,
|
||||
styleActiveLine: true,
|
||||
highlightSelectionMatches: {
|
||||
annotateScrollbar: true
|
||||
},
|
||||
viewportMargin: 50,
|
||||
inputStyle: 'contenteditable',
|
||||
allowDropFileTypes: ['image/jpg', 'image/png', 'image/svg', 'image/jpeg', 'image/gif']
|
||||
})
|
||||
this.cm.setValue(this.$store.get('editor/content'))
|
||||
this.cm.on('change', c => {
|
||||
this.$store.set('editor/content', c.getValue())
|
||||
})
|
||||
if (this.$vuetify.breakpoint.mdAndUp) {
|
||||
this.cm.setSize(null, 'calc(100vh - 64px - 24px)')
|
||||
} else {
|
||||
this.cm.setSize(null, 'calc(100vh - 56px - 16px)')
|
||||
}
|
||||
|
||||
// Set Keybindings
|
||||
|
||||
const keyBindings = {
|
||||
'F11' (c) {
|
||||
c.setOption('fullScreen', !c.getOption('fullScreen'))
|
||||
},
|
||||
'Esc' (c) {
|
||||
if (c.getOption('fullScreen')) c.setOption('fullScreen', false)
|
||||
}
|
||||
}
|
||||
this.cm.setOption('extraKeys', keyBindings)
|
||||
|
||||
// Handle cursor movement
|
||||
|
||||
this.cm.on('cursorActivity', c => {
|
||||
this.positionSync(c)
|
||||
})
|
||||
|
||||
// Render initial preview
|
||||
|
||||
this.$root.$on('editorInsert', opts => {
|
||||
switch (opts.kind) {
|
||||
case 'IMAGE':
|
||||
let img = `<img src="${opts.path}" alt="${opts.text}"`
|
||||
if (opts.align && opts.align !== '') {
|
||||
img += ` class="align-${opts.align}"`
|
||||
}
|
||||
img += ` />`
|
||||
this.insertAtCursor({
|
||||
content: img
|
||||
})
|
||||
break
|
||||
case 'BINARY':
|
||||
this.insertAtCursor({
|
||||
content: `<a href="${opts.path}" title="${opts.text}">${opts.text}</a>`
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
// Handle save conflict
|
||||
this.$root.$on('saveConflict', () => {
|
||||
this.toggleModal(`editorModalConflict`)
|
||||
})
|
||||
this.$root.$on('overwriteEditorContent', () => {
|
||||
this.cm.setValue(this.$store.get('editor/content'))
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('editorInsert')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
$editor-height: calc(100vh - 64px - 24px);
|
||||
$editor-height-mobile: calc(100vh - 56px - 16px);
|
||||
|
||||
.editor-code {
|
||||
&-main {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-editor {
|
||||
background-color: darken(mc('grey', '900'), 4.5%);
|
||||
flex: 1 1 50%;
|
||||
display: block;
|
||||
height: $editor-height;
|
||||
position: relative;
|
||||
|
||||
&-title {
|
||||
background-color: mc('grey', '800');
|
||||
border-bottom-left-radius: 5px;
|
||||
display: inline-flex;
|
||||
height: 30px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
color: mc('grey', '500');
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 7;
|
||||
text-transform: uppercase;
|
||||
font-size: .7rem;
|
||||
cursor: pointer;
|
||||
|
||||
@include until($tablet) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-sidebar {
|
||||
background-color: mc('grey', '900');
|
||||
width: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 24px 0;
|
||||
|
||||
@include until($tablet) {
|
||||
padding: 12px 0;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&-sysbar {
|
||||
padding-left: 0;
|
||||
|
||||
&-locale {
|
||||
background-color: rgba(255,255,255,.25);
|
||||
display:inline-flex;
|
||||
padding: 0 12px;
|
||||
height: 24px;
|
||||
width: 63px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// CODE MIRROR
|
||||
// ==========================================
|
||||
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
|
||||
.cm-header-1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.cm-header-2 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.cm-header-3 {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
.cm-header-4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.cm-header-5 {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.cm-header-6 {
|
||||
font-size: 1.025rem;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-focused .cm-matchhighlight {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.cm-matchhighlight {
|
||||
background-color: mc('grey', '800');
|
||||
}
|
||||
.CodeMirror-selection-highlight-scrollbar {
|
||||
background-color: mc('green', '600');
|
||||
}
|
||||
|
||||
.cm-s-wikijs-dark.CodeMirror {
|
||||
background: darken(mc('grey','900'), 3%);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.cm-s-wikijs-dark div.CodeMirror-selected {
|
||||
background: mc('blue','800');
|
||||
}
|
||||
.cm-s-wikijs-dark .cm-matchhighlight {
|
||||
background: mc('blue','800');
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-line::selection, .cm-s-wikijs-dark .CodeMirror-line > span::selection, .cm-s-wikijs-dark .CodeMirror-line > span > span::selection {
|
||||
background: mc('amber', '500');
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-line::-moz-selection, .cm-s-wikijs-dark .CodeMirror-line > span::-moz-selection, .cm-s-wikijs-dark .CodeMirror-line > span > span::-moz-selection {
|
||||
background: mc('amber', '500');
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-gutters {
|
||||
background: darken(mc('grey','900'), 6%);
|
||||
border-right: 1px solid mc('grey','900');
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-guttermarker {
|
||||
color: #ac4142;
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-guttermarker-subtle {
|
||||
color: #505050;
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-linenumber {
|
||||
color: mc('grey','800');
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-cursor {
|
||||
border-left: 1px solid #b0b0b0;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-comment {
|
||||
color: mc('orange','800');
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-atom {
|
||||
color: #aa759f;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-number {
|
||||
color: #aa759f;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-property, .cm-s-wikijs-dark span.cm-attribute {
|
||||
color: #90a959;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-keyword {
|
||||
color: #ac4142;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-string {
|
||||
color: #f4bf75;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-variable {
|
||||
color: #90a959;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-variable-2 {
|
||||
color: #6a9fb5;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-def {
|
||||
color: #d28445;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-bracket {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-tag {
|
||||
color: #ac4142;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-link {
|
||||
color: #aa759f;
|
||||
}
|
||||
.cm-s-wikijs-dark span.cm-error {
|
||||
background: #ac4142;
|
||||
color: #b0b0b0;
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-activeline-background {
|
||||
background: mc('grey','900');
|
||||
}
|
||||
.cm-s-wikijs-dark .CodeMirror-matchingbracket {
|
||||
text-decoration: underline;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
@ -1,81 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-card.editor-modal-blocks.animated.fadeInLeft(flat, tile)
|
||||
v-container.pa-3(grid-list-lg, fluid)
|
||||
v-row(dense)
|
||||
v-col(
|
||||
v-for='(item, idx) of blocks'
|
||||
:key='`block-` + item.key'
|
||||
cols='12'
|
||||
lg='4'
|
||||
xl='3'
|
||||
)
|
||||
v-card.radius-7(light, flat, @click='selectBlock(item)')
|
||||
v-card-text
|
||||
.d-flex.align-center
|
||||
v-avatar.radius-7(color='teal')
|
||||
v-icon(dark) {{item.icon}}
|
||||
.pl-3
|
||||
.body-2: strong.teal--text {{item.title}}
|
||||
.caption.grey--text {{item.description}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { sync } from 'vuex-pathify'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
blocks: [
|
||||
{
|
||||
key: 'childlist',
|
||||
title: 'List Children Pages',
|
||||
description: 'Display a links list of all children of this page.',
|
||||
icon: 'mdi-format-list-text'
|
||||
},
|
||||
{
|
||||
key: 'tabs',
|
||||
title: 'Tabs',
|
||||
description: 'Organize content within tabs.',
|
||||
icon: 'mdi-tab'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
},
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
selectBlock (item) {
|
||||
this.block = _.cloneDeep(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.editor-modal-blocks {
|
||||
position: fixed;
|
||||
top: 112px;
|
||||
left: 64px;
|
||||
z-index: 10;
|
||||
width: calc(100vw - 64px - 17px);
|
||||
height: calc(100vh - 112px - 24px);
|
||||
background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important;
|
||||
|
||||
@include until($tablet) {
|
||||
left: 40px;
|
||||
width: calc(100vw - 40px);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,222 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-card.editor-modal-conflict.animated.fadeIn(flat, tile)
|
||||
.pa-4
|
||||
v-toolbar.radius-7(flat, color='indigo', style='border-bottom-left-radius: 0; border-bottom-right-radius: 0;', dark)
|
||||
v-icon.mr-3 mdi-merge
|
||||
.subtitle-1 {{$t('editor:conflict.title')}}
|
||||
v-spacer
|
||||
v-btn(outlined, color='white', @click='useLocal', :title='$t(`editor:conflict.useLocalHint`)')
|
||||
v-icon(left) mdi-alpha-l-box
|
||||
span {{$t('editor:conflict.useLocal')}}
|
||||
v-dialog(
|
||||
v-model='isRemoteConfirmDiagShown'
|
||||
width='500'
|
||||
)
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.ml-3(outlined, color='white', v-on='on', :title='$t(`editor:conflict.useRemoteHint`)')
|
||||
v-icon(left) mdi-alpha-r-box
|
||||
span {{$t('editor:conflict.useRemote')}}
|
||||
v-card
|
||||
.dialog-header.is-short.is-indigo
|
||||
v-icon.mr-3(color='white') mdi-alpha-r-box
|
||||
span {{$t('editor:conflict.overwrite.title')}}
|
||||
v-card-text.pa-4
|
||||
i18next.body-2(tag='div', path='editor:conflict.overwrite.description')
|
||||
strong(place='refEditsLost') {{$t('editor:conflict.overwrite.editsLost')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(outlined, color='indigo', @click='isRemoteConfirmDiagShown = false')
|
||||
v-icon(left) mdi-close
|
||||
span {{$t('common:actions.cancel')}}
|
||||
v-btn(@click='useRemote', color='indigo', dark)
|
||||
v-icon(left) mdi-check
|
||||
span {{$t('common:actions.confirm')}}
|
||||
v-divider.mx-3(vertical)
|
||||
v-btn(outlined, color='indigo lighten-4', @click='close')
|
||||
v-icon(left) mdi-close
|
||||
span {{$t('common:actions.cancel')}}
|
||||
v-row.indigo.darken-1.body-2(no-gutters)
|
||||
v-col.pa-4
|
||||
v-icon.mr-3(color='white') mdi-alpha-l-box
|
||||
i18next.white--text(tag='span', path='editor:conflict.localVersion')
|
||||
em.indigo--text.text--lighten-4(place='refEditable') {{$t('editor:conflict.editable')}}
|
||||
v-divider(vertical)
|
||||
v-col.pa-4
|
||||
v-icon.mr-3(color='white') mdi-alpha-r-box
|
||||
i18next.white--text(tag='span', path='editor:conflict.remoteVersion')
|
||||
em.indigo--text.text--lighten-4(place='refReadOnly') {{$t('editor:conflict.readonly')}}
|
||||
v-row.grey.lighten-2.body-2(no-gutters)
|
||||
v-col.px-4.py-2
|
||||
i18next.grey--text.text--darken-2(tag='em', path='editor:conflict.leftPanelInfo')
|
||||
span(place='date', :title='$options.filters.moment(checkoutDateActive, `LLL`)') {{ checkoutDateActive | moment('from') }}
|
||||
v-divider(vertical)
|
||||
v-col.px-4.py-2
|
||||
i18next.grey--text.text--darken-2(tag='em', path='editor:conflict.rightPanelInfo')
|
||||
strong(place='authorName') {{latest.authorName}}
|
||||
span(place='date', :title='$options.filters.moment(latest.updatedAt, `LLL`)') {{ latest.updatedAt | moment('from') }}
|
||||
v-row.grey.lighten-3.grey--text.text--darken-3(no-gutters)
|
||||
v-col.pa-4
|
||||
.body-2
|
||||
strong.indigo--text {{$t('editor:conflict.pageTitle')}}
|
||||
strong.pl-2 {{title}}
|
||||
.caption
|
||||
strong.indigo--text {{$t('editor:conflict.pageDescription')}}
|
||||
span.pl-2 {{description}}
|
||||
v-divider(vertical, light)
|
||||
v-col.pa-4
|
||||
.body-2
|
||||
strong.indigo--text {{$t('editor:conflict.pageTitle')}}
|
||||
strong.pl-2 {{latest.title}}
|
||||
.caption
|
||||
strong.indigo--text {{$t('editor:conflict.pageDescription')}}
|
||||
span.pl-2 {{latest.description}}
|
||||
v-card.radius-7(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark')
|
||||
div(ref='cm')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
import { sync, get } from 'vuex-pathify'
|
||||
|
||||
/* global siteConfig */
|
||||
|
||||
// ========================================
|
||||
// IMPORTS
|
||||
// ========================================
|
||||
|
||||
import '../../libs/codemirror-merge/diff-match-patch.js'
|
||||
|
||||
// Code Mirror
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
|
||||
// Language
|
||||
import 'codemirror/mode/markdown/markdown.js'
|
||||
import 'codemirror/mode/htmlmixed/htmlmixed.js'
|
||||
|
||||
// Addons
|
||||
import 'codemirror/addon/selection/active-line.js'
|
||||
import 'codemirror/addon/merge/merge.js'
|
||||
import 'codemirror/addon/merge/merge.css'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cm: null,
|
||||
latest: {
|
||||
title: '',
|
||||
description: '',
|
||||
updatedAt: '',
|
||||
authorName: ''
|
||||
},
|
||||
isRemoteConfirmDiagShown: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editorKey: get('editor/editorKey'),
|
||||
activeModal: sync('editor/activeModal'),
|
||||
pageId: get('page/id'),
|
||||
title: get('page/title'),
|
||||
description: get('page/description'),
|
||||
updatedAt: get('page/updatedAt'),
|
||||
checkoutDateActive: sync('editor/checkoutDateActive')
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.isRemoteConfirmDiagShown = false
|
||||
this.activeModal = ''
|
||||
},
|
||||
overwriteAndClose() {
|
||||
this.checkoutDateActive = this.latest.updatedAt
|
||||
this.$root.$emit('overwriteEditorContent')
|
||||
this.$root.$emit('resetEditorConflict')
|
||||
this.close()
|
||||
},
|
||||
useLocal () {
|
||||
this.$store.set('editor/content', this.cm.edit.getValue())
|
||||
this.overwriteAndClose()
|
||||
},
|
||||
useRemote () {
|
||||
this.$store.set('editor/content', this.latest.content)
|
||||
this.overwriteAndClose()
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
let textMode = 'text/html'
|
||||
|
||||
switch (this.editorKey) {
|
||||
case 'markdown':
|
||||
textMode = 'text/markdown'
|
||||
break
|
||||
}
|
||||
|
||||
let resp = await this.$apollo.query({
|
||||
query: gql`
|
||||
query ($id: Int!) {
|
||||
pages {
|
||||
conflictLatest(id: $id) {
|
||||
id
|
||||
authorId
|
||||
authorName
|
||||
content
|
||||
createdAt
|
||||
description
|
||||
isPublished
|
||||
locale
|
||||
path
|
||||
tags
|
||||
title
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'network-only',
|
||||
variables: {
|
||||
id: this.$store.get('page/id')
|
||||
}
|
||||
})
|
||||
resp = _.get(resp, 'data.pages.conflictLatest', false)
|
||||
|
||||
if (!resp) {
|
||||
return this.$store.commit('showNotification', {
|
||||
message: 'Failed to fetch latest version.',
|
||||
style: 'warning',
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
this.latest = resp
|
||||
|
||||
this.cm = CodeMirror.MergeView(this.$refs.cm, {
|
||||
value: this.$store.get('editor/content'),
|
||||
orig: resp.content,
|
||||
tabSize: 2,
|
||||
mode: textMode,
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
connect: null,
|
||||
highlightDifferences: true,
|
||||
styleActiveLine: true,
|
||||
collapseIdentical: true,
|
||||
direction: siteConfig.rtl ? 'rtl' : 'ltr'
|
||||
})
|
||||
this.cm.rightOriginal().setSize(null, 'calc(100vh - 265px)')
|
||||
this.cm.editor().setSize(null, 'calc(100vh - 265px)')
|
||||
this.cm.wrap.style.height = 'calc(100vh - 265px)'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.editor-modal-conflict {
|
||||
position: fixed !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, .9) !important;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
@ -1,126 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-card.editor-modal-drawio.animated.fadeIn(flat, tile)
|
||||
iframe(
|
||||
ref='drawio'
|
||||
src='https://embed.diagrams.net/?embed=1&proto=json&spin=1&saveAndExit=1&noSaveBtn=1&noExitBtn=0'
|
||||
frameborder='0'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { sync, get } from 'vuex-pathify'
|
||||
|
||||
// const xmlTest = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
// <mxfile version="13.4.2">
|
||||
// <diagram id="SgbkCjxR32CZT1FvBvkp" name="Page-1">
|
||||
// <mxGraphModel dx="2062" dy="1123" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
// <root>
|
||||
// <mxCell id="0" />
|
||||
// <mxCell id="1" parent="0" />
|
||||
// <mxCell id="5gE3BTvRYS_8FoJnOusC-1" value="" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
|
||||
// <mxGeometry x="380" y="530" width="80" height="80" as="geometry" />
|
||||
// </mxCell>
|
||||
// </root>
|
||||
// </mxGraphModel>
|
||||
// </diagram>
|
||||
// </mxfile>
|
||||
// `
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editorKey: get('editor/editorKey'),
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.activeModal = ''
|
||||
},
|
||||
overwriteAndClose() {
|
||||
this.$root.$emit('overwriteEditorContent')
|
||||
this.$root.$emit('resetEditorConflict')
|
||||
this.close()
|
||||
},
|
||||
send (msg) {
|
||||
this.$refs.drawio.contentWindow.postMessage(JSON.stringify(msg), '*')
|
||||
},
|
||||
receive (evt) {
|
||||
if (evt.frame === null || evt.source !== this.$refs.drawio.contentWindow || evt.data.length < 1) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const msg = JSON.parse(evt.data)
|
||||
switch (msg.event) {
|
||||
case 'init': {
|
||||
this.send({
|
||||
action: 'load',
|
||||
autosave: 0,
|
||||
modified: 'unsavedChanges',
|
||||
xml: this.$store.get('editor/activeModalData'),
|
||||
title: this.$store.get('page/title')
|
||||
})
|
||||
this.$store.set('editor/activeModalData', null)
|
||||
break
|
||||
}
|
||||
case 'save': {
|
||||
if (msg.exit) {
|
||||
this.send({
|
||||
action: 'export',
|
||||
format: 'xmlsvg'
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'export': {
|
||||
const svgDataStart = msg.data.indexOf('base64,') + 7
|
||||
this.$root.$emit('editorInsert', {
|
||||
kind: 'DIAGRAM',
|
||||
text: msg.data.slice(svgDataStart)
|
||||
// text: msg.xml.replace(/ agent="(.*?)"/, '').replace(/ host="(.*?)"/, '').replace(/ etag="(.*?)"/, '')
|
||||
})
|
||||
this.close()
|
||||
break
|
||||
}
|
||||
case 'exit': {
|
||||
this.close()
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
window.addEventListener('message', this.receive)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('message', this.receive)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.editor-modal-drawio {
|
||||
position: fixed !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: rgba(255,255,255, 1) !important;
|
||||
overflow: hidden;
|
||||
|
||||
> iframe {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,90 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-dialog(v-model='isShown', persistent, max-width='700', no-click-animation)
|
||||
v-btn(fab, fixed, bottom, right, color='grey darken-3', dark, @click='goBack', style='width: 50px;'): v-icon mdi-undo-variant
|
||||
v-card.radius-7(color='blue darken-3', dark)
|
||||
v-card-text.text-center.py-4
|
||||
.subtitle-1.white--text {{$t('editor:select.title')}}
|
||||
v-container(grid-list-lg, fluid)
|
||||
v-layout(row, wrap, justify-center)
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.animated.fadeInUp.wait-p2s(
|
||||
hover
|
||||
light
|
||||
ripple
|
||||
)
|
||||
v-card-text.text-center(@click='selectEditor("code")')
|
||||
img(src='/_assets-legacy/svg/editor-icon-code.svg', alt='Code', style='width: 36px;')
|
||||
.body-2.primary--text.mt-2 Code
|
||||
.caption.grey--text Raw HTML
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.animated.fadeInUp.wait-p1s(
|
||||
hover
|
||||
light
|
||||
ripple
|
||||
)
|
||||
v-card-text.text-center(@click='selectEditor("markdown")')
|
||||
img(src='/_assets-legacy/svg/editor-icon-markdown.svg', alt='Markdown', style='width: 36px;')
|
||||
.body-2.primary--text.mt-2 Markdown
|
||||
.caption.grey--text Plain Text Formatting
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.animated.fadeInUp.wait-p3s(
|
||||
hover
|
||||
light
|
||||
ripple
|
||||
)
|
||||
v-card-text.text-center(@click='selectEditor("ckeditor")')
|
||||
img(src='/_assets-legacy/svg/editor-icon-ckeditor.svg', alt='Visual Editor', style='width: 36px;')
|
||||
.body-2.mt-2.primary--text Visual Editor
|
||||
.caption.grey--text Rich-text WYSIWYG
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { sync, get } from 'vuex-pathify'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templateDialogIsShown: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
},
|
||||
currentEditor: sync('editor/editor'),
|
||||
locale: get('page/locale'),
|
||||
path: get('page/path')
|
||||
},
|
||||
methods: {
|
||||
selectEditor (name) {
|
||||
this.currentEditor = `editor${_.startCase(name)}`
|
||||
this.isShown = false
|
||||
},
|
||||
goBack () {
|
||||
window.history.go(-1)
|
||||
},
|
||||
fromTemplate () {
|
||||
this.templateDialogIsShown = true
|
||||
},
|
||||
fromTemplateHandle ({ id }) {
|
||||
this.templateDialogIsShown = false
|
||||
this.isShown = false
|
||||
this.$nextTick(() => {
|
||||
window.location.assign(`/e/${this.locale}/${this.path}?from=${id}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
@ -1,629 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-card.editor-modal-media.animated.fadeInLeft(flat, tile, :class='`is-editor-` + editorKey')
|
||||
v-container.pa-3(grid-list-lg, fluid)
|
||||
v-layout(row, wrap)
|
||||
v-flex(xs12, lg9)
|
||||
v-card.radius-7.animated.fadeInLeft.wait-p1s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark')
|
||||
v-card-text
|
||||
.d-flex
|
||||
v-toolbar.radius-7(:color='$vuetify.theme.dark ? `teal` : `teal lighten-5`', dense, flat, height='44')
|
||||
.body-2(:class='$vuetify.theme.dark ? `white--text` : `teal--text`') {{$t('editor:assets.title')}}
|
||||
v-spacer
|
||||
v-btn(text, icon, @click='refresh')
|
||||
v-icon(:color='$vuetify.theme.dark ? `white` : `teal`') mdi-refresh
|
||||
v-dialog(v-model='newFolderDialog', max-width='550')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.ml-3.my-0.mr-0.radius-7(outlined, large, color='teal', :icon='$vuetify.breakpoint.xsOnly', v-on='on')
|
||||
v-icon(:left='$vuetify.breakpoint.mdAndUp') mdi-plus
|
||||
span.hidden-sm-and-down(:class='$vuetify.theme.dark ? `teal--text text--lighten-3` : ``') {{$t('editor:assets.newFolder')}}
|
||||
v-card
|
||||
.dialog-header.is-short.subtitle-1 {{$t('editor:assets.newFolder')}}
|
||||
v-card-text.pt-5
|
||||
v-text-field.md2(
|
||||
outlined
|
||||
prepend-icon='mdi-folder-outline'
|
||||
v-model='newFolderName'
|
||||
:label='$t(`editor:assets.folderName`)'
|
||||
counter='255'
|
||||
@keyup.enter='createFolder'
|
||||
@keyup.esc='newFolderDialog = false'
|
||||
ref='folderNameIpt'
|
||||
)
|
||||
i18next.caption.grey--text.text--darken-1.pl-5(path='editor:assets.folderNameNamingRules', tag='div')
|
||||
a(place='namingRules', href='https://docs-beta.requarks.io/guide/assets#naming-restrictions', target='_blank') {{$t('editor:assets.folderNameNamingRulesLink')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='newFolderDialog = false') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-3(color='primary', @click='createFolder', :disabled='!isFolderNameValid', :loading='newFolderLoading') {{$t('common:actions.create')}}
|
||||
v-toolbar(flat, dense, :color='$vuetify.theme.dark ? `grey darken-3` : `white`')
|
||||
template(v-if='folderTree.length > 0')
|
||||
.body-2
|
||||
span.mr-1 /
|
||||
template(v-for='folder of folderTree')
|
||||
span(:key='folder.id') {{folder.name}}
|
||||
span.mx-1 /
|
||||
.body-2(v-else) / #[em root]
|
||||
template(v-if='folders.length > 0 || currentFolderId > 0')
|
||||
v-btn.is-icon.mx-1(:color='$vuetify.theme.dark ? `grey lighten-1` : `grey darken-2`', outlined, :dark='currentFolderId > 0', @click='upFolder()', :disabled='currentFolderId === 0')
|
||||
v-icon mdi-folder-upload
|
||||
v-btn.btn-normalcase.mx-1(v-for='folder of folders', :key='folder.id', depressed, color='grey darken-2', dark, @click='downFolder(folder)')
|
||||
v-icon(left) mdi-folder
|
||||
span.caption(style='text-transform: none;') {{ folder.name }}
|
||||
v-divider.mt-2
|
||||
v-data-table(
|
||||
:items='assets'
|
||||
:headers='headers'
|
||||
:page.sync='pagination'
|
||||
:items-per-page='15'
|
||||
:loading='loading'
|
||||
must-sort,
|
||||
sort-by='ID',
|
||||
sort-desc,
|
||||
hide-default-footer,
|
||||
dense
|
||||
)
|
||||
template(slot='item', slot-scope='props')
|
||||
tr.is-clickable(
|
||||
@click.left='currentFileId = props.item.id'
|
||||
@click.right.prevent=''
|
||||
:class='currentFileId === props.item.id ? ($vuetify.theme.dark ? `grey darken-3-d5` : `teal lighten-5`) : ``'
|
||||
)
|
||||
td.caption(v-if='$vuetify.breakpoint.smAndUp') {{ props.item.id }}
|
||||
td
|
||||
.body-2: strong(:class='currentFileId === props.item.id ? `teal--text` : ``') {{ props.item.filename }}
|
||||
.caption.grey--text {{ props.item.description }}
|
||||
td.text-xs-center(v-if='$vuetify.breakpoint.lgAndUp')
|
||||
v-chip.ma-0(x-small, :color='$vuetify.theme.dark ? `grey darken-4` : `grey lighten-4`')
|
||||
.overline {{props.item.ext.toUpperCase().substring(1)}}
|
||||
td.caption(v-if='$vuetify.breakpoint.mdAndUp') {{ props.item.fileSize | prettyBytes }}
|
||||
td.caption(v-if='$vuetify.breakpoint.mdAndUp') {{ props.item.createdAt | moment('from') }}
|
||||
td(v-if='$vuetify.breakpoint.smAndUp')
|
||||
v-menu(offset-x, min-width='200')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon, v-on='on', tile, small, @click.left='currentFileId = props.item.id')
|
||||
v-icon(color='grey darken-2') mdi-dots-horizontal
|
||||
v-list(nav, style='border-top: 5px solid #444;')
|
||||
v-list-item(@click='', disabled)
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='teal') mdi-text-short
|
||||
v-list-item-content {{$t('common:actions.properties')}}
|
||||
template(v-if='props.item.kind === `IMAGE`')
|
||||
v-list-item(@click='previewDialog = true', disabled)
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='green') mdi-image-search-outline
|
||||
v-list-item-content {{$t('common:actions.preview')}}
|
||||
v-list-item(@click='', disabled)
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='indigo') mdi-crop-rotate
|
||||
v-list-item-content {{$t('common:actions.edit')}}
|
||||
v-list-item(@click='', disabled)
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='purple') mdi-flash-circle
|
||||
v-list-item-content {{$t('common:actions.optimize')}}
|
||||
v-list-item(@click='openRenameDialog')
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='orange') mdi-keyboard-outline
|
||||
v-list-item-content {{$t('common:actions.rename')}}
|
||||
v-list-item(@click='', disabled)
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='blue') mdi-file-move
|
||||
v-list-item-content {{$t('common:actions.move')}}
|
||||
v-list-item(@click='deleteDialog = true')
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='red') mdi-file-hidden
|
||||
v-list-item-content {{$t('common:actions.delete')}}
|
||||
template(slot='no-data')
|
||||
v-alert.mt-3.radius-7(icon='mdi-folder-open-outline', :value='true', outlined, color='teal') {{$t('editor:assets.folderEmpty')}}
|
||||
.text-xs-center.py-2(v-if='this.pageTotal > 1')
|
||||
v-pagination(v-model='pagination', :length='pageTotal', color='teal')
|
||||
.d-flex.mt-3
|
||||
v-toolbar.radius-7(flat, :color='$vuetify.theme.dark ? `grey darken-2` : `grey lighten-4`', dense, height='44')
|
||||
.body-2(:class='$vuetify.theme.dark ? `grey--text text--lighten-1` : `grey--text text--darken-1`') {{$t('editor:assets.fileCount', { count: assets.length })}}
|
||||
v-btn.ml-3.mr-0.my-0.radius-7(color='red darken-2', large, @click='cancel', dark)
|
||||
v-icon(left) mdi-close
|
||||
span {{$t('common:actions.cancel')}}
|
||||
v-btn.ml-3.mr-0.my-0.radius-7(color='teal', large, @click='insert', :disabled='!currentFileId', :dark='currentFileId !== null')
|
||||
v-icon(left) mdi-playlist-plus
|
||||
span {{$t('common:actions.insert')}}
|
||||
|
||||
v-flex(xs12, lg3)
|
||||
v-card.radius-7.animated.fadeInRight.wait-p3s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark')
|
||||
v-card-text
|
||||
.d-flex
|
||||
v-toolbar.radius-7(:color='$vuetify.theme.dark ? `teal` : `teal lighten-5`', dense, flat, height='44')
|
||||
v-icon.mr-3(:color='$vuetify.theme.dark ? `white` : `teal`') mdi-cloud-upload
|
||||
.body-2(:class='$vuetify.theme.dark ? `white--text` : `teal--text`') {{$t('editor:assets.uploadAssets')}}
|
||||
v-btn.my-0.ml-3.mr-0.radius-7(outlined, large, color='teal', @click='browse', v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-icon(left) mdi-plus-box-multiple
|
||||
span(:class='$vuetify.theme.dark ? `teal--text text--lighten-3` : ``') {{$t('common:actions.browse')}}
|
||||
file-pond.mt-3(
|
||||
name='mediaUpload'
|
||||
ref='pond'
|
||||
:label-idle='$t(`editor:assets.uploadAssetsDropZone`)'
|
||||
allow-multiple='true'
|
||||
:files='files'
|
||||
max-files='10'
|
||||
:server='filePondServerOpts'
|
||||
:instant-upload='false'
|
||||
:allow-revert='false'
|
||||
@processfile='onFileProcessed'
|
||||
)
|
||||
v-divider
|
||||
v-card-actions.pa-3
|
||||
.caption.grey--text.text-darken-2 Max 10 files, 5 MB each
|
||||
v-spacer
|
||||
v-btn.px-4(color='teal', dark, @click='upload') {{$t('common:actions.upload')}}
|
||||
|
||||
v-card.mt-3.radius-7.animated.fadeInRight.wait-p4s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark')
|
||||
v-card-text.pb-0
|
||||
v-toolbar.radius-7(:color='$vuetify.theme.dark ? `teal` : `teal lighten-5`', dense, flat)
|
||||
v-icon.mr-3(:color='$vuetify.theme.dark ? `white` : `teal`') mdi-cloud-download
|
||||
.body-2(:class='$vuetify.theme.dark ? `white--text` : `teal--text`') {{$t('editor:assets.fetchImage')}}
|
||||
v-spacer
|
||||
v-chip(label, color='white', small).teal--text coming soon
|
||||
v-text-field.mt-3(
|
||||
v-model='remoteImageUrl'
|
||||
outlined
|
||||
color='teal'
|
||||
single-line
|
||||
placeholder='https://example.com/image.jpg'
|
||||
)
|
||||
v-divider
|
||||
v-card-actions.pa-3
|
||||
.caption.grey--text.text-darken-2 Max 5 MB
|
||||
v-spacer
|
||||
v-btn.px-4(color='teal', disabled) {{$t('common:actions.fetch')}}
|
||||
|
||||
v-card.mt-3.radius-7.animated.fadeInRight.wait-p4s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark')
|
||||
v-card-text.pb-0
|
||||
v-toolbar.radius-7(:color='$vuetify.theme.dark ? `teal` : `teal lighten-5`', dense, flat)
|
||||
v-icon.mr-3(:color='$vuetify.theme.dark ? `white` : `teal`') mdi-format-align-top
|
||||
.body-2(:class='$vuetify.theme.dark ? `white--text` : `teal--text`') {{$t('editor:assets.imageAlign')}}
|
||||
v-select.mt-3(
|
||||
v-model='imageAlignment'
|
||||
:items='imageAlignments'
|
||||
outlined
|
||||
single-line
|
||||
color='teal'
|
||||
placeholder='None'
|
||||
)
|
||||
|
||||
//- RENAME DIALOG
|
||||
|
||||
v-dialog(v-model='renameDialog', max-width='550', persistent)
|
||||
v-card
|
||||
.dialog-header.is-short.is-orange
|
||||
v-icon.mr-2(color='white') mdi-keyboard
|
||||
span {{$t('editor:assets.renameAsset')}}
|
||||
v-card-text.pt-5
|
||||
.body-2 {{$t('editor:assets.renameAssetSubtitle')}}
|
||||
v-text-field(
|
||||
outlined
|
||||
single-line
|
||||
:counter='255'
|
||||
v-model='renameAssetName'
|
||||
@keyup.enter='renameAsset'
|
||||
:disabled='renameAssetLoading'
|
||||
)
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='renameDialog = false', :disabled='renameAssetLoading') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-3(color='orange darken-3', @click='renameAsset', :loading='renameAssetLoading').white--text {{$t('common:actions.rename')}}
|
||||
|
||||
//- DELETE DIALOG
|
||||
|
||||
v-dialog(v-model='deleteDialog', max-width='550', persistent)
|
||||
v-card
|
||||
.dialog-header.is-short.is-red
|
||||
v-icon.mr-2(color='white') mdi-trash-can-outline
|
||||
span {{$t('editor:assets.deleteAsset')}}
|
||||
v-card-text.pt-5
|
||||
.body-2 {{$t('editor:assets.deleteAssetConfirm')}}
|
||||
.body-2.red--text.text--darken-2 {{currentAsset.filename}}?
|
||||
.caption.mt-3 {{$t('editor:assets.deleteAssetWarn')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='deleteDialog = false', :disabled='deleteAssetLoading') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-3(color='red darken-2', @click='deleteAsset', :loading='deleteAssetLoading').white--text {{$t('common:actions.delete')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
import Cookies from 'js-cookie'
|
||||
import vueFilePond from 'vue-filepond'
|
||||
import 'filepond/dist/filepond.min.css'
|
||||
|
||||
import listAssetQuery from 'gql/editor/editor-media-query-list.gql'
|
||||
import listFolderAssetQuery from 'gql/editor/editor-media-query-folder-list.gql'
|
||||
import createAssetFolderMutation from 'gql/editor/editor-media-mutation-folder-create.gql'
|
||||
import renameAssetMutation from 'gql/editor/editor-media-mutation-asset-rename.gql'
|
||||
import deleteAssetMutation from 'gql/editor/editor-media-mutation-asset-delete.gql'
|
||||
|
||||
const FilePond = vueFilePond()
|
||||
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
|
||||
const disallowedFolderChars = /[A-Z()=.!@#$%?&*+`~<>,;:\\/[\]¬{| ]/
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FilePond
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
folders: [],
|
||||
files: [],
|
||||
assets: [],
|
||||
pagination: 1,
|
||||
remoteImageUrl: '',
|
||||
imageAlignments: [
|
||||
{ text: 'None', value: '' },
|
||||
{ text: 'Left', value: 'left' },
|
||||
{ text: 'Centered', value: 'center' },
|
||||
{ text: 'Right', value: 'right' },
|
||||
{ text: 'Absolute Top Right', value: 'abstopright' }
|
||||
],
|
||||
imageAlignment: '',
|
||||
loading: false,
|
||||
newFolderDialog: false,
|
||||
newFolderName: '',
|
||||
newFolderLoading: false,
|
||||
previewDialog: false,
|
||||
renameDialog: false,
|
||||
renameAssetName: '',
|
||||
renameAssetLoading: false,
|
||||
deleteDialog: false,
|
||||
deleteAssetLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
},
|
||||
editorKey: get('editor/editorKey'),
|
||||
activeModal: sync('editor/activeModal'),
|
||||
folderTree: get('editor/media@folderTree'),
|
||||
currentFolderId: sync('editor/media@currentFolderId'),
|
||||
currentFileId: sync('editor/media@currentFileId'),
|
||||
pageTotal () {
|
||||
if (!this.assets) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Math.ceil(this.assets.length / 15)
|
||||
},
|
||||
headers() {
|
||||
return _.compact([
|
||||
this.$vuetify.breakpoint.smAndUp && { text: this.$t('editor:assets.headerId'), value: 'id', width: 80 },
|
||||
{ text: this.$t('editor:assets.headerFilename'), value: 'filename' },
|
||||
this.$vuetify.breakpoint.lgAndUp && { text: this.$t('editor:assets.headerType'), value: 'ext', width: 90 },
|
||||
this.$vuetify.breakpoint.mdAndUp && { text: this.$t('editor:assets.headerFileSize'), value: 'fileSize', width: 110 },
|
||||
this.$vuetify.breakpoint.mdAndUp && { text: this.$t('editor:assets.headerAdded'), value: 'createdAt', width: 175 },
|
||||
this.$vuetify.breakpoint.smAndUp && { text: this.$t('editor:assets.headerActions'), value: '', width: 80, sortable: false, align: 'right' }
|
||||
])
|
||||
},
|
||||
isFolderNameValid() {
|
||||
return this.newFolderName.length > 1 && !localeSegmentRegex.test(this.newFolderName) && !disallowedFolderChars.test(this.newFolderName)
|
||||
},
|
||||
currentAsset () {
|
||||
return _.find(this.assets, ['id', this.currentFileId]) || {}
|
||||
},
|
||||
filePondServerOpts () {
|
||||
const jwtToken = Cookies.get('jwt')
|
||||
return {
|
||||
process: {
|
||||
url: '/u',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${jwtToken}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
newFolderDialog(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.folderNameIpt.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
prettyBytes(num) {
|
||||
if (typeof num !== 'number' || isNaN(num)) {
|
||||
throw new TypeError('Expected a number')
|
||||
}
|
||||
|
||||
let exponent
|
||||
let unit
|
||||
let neg = num < 0
|
||||
let units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
|
||||
if (neg) {
|
||||
num = -num
|
||||
}
|
||||
if (num < 1) {
|
||||
return (neg ? '-' : '') + num + ' B'
|
||||
}
|
||||
exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), units.length - 1)
|
||||
num = (num / Math.pow(1000, exponent)).toFixed(2) * 1
|
||||
unit = units[exponent]
|
||||
|
||||
return (neg ? '-' : '') + num + ' ' + unit
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async refresh() {
|
||||
await this.$apollo.queries.assets.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:assets.refreshSuccess'),
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
},
|
||||
insert () {
|
||||
const asset = _.find(this.assets, ['id', this.currentFileId])
|
||||
const assetPath = this.folderTree.map(f => f.slug).join('/')
|
||||
this.$root.$emit('editorInsert', {
|
||||
kind: asset.kind,
|
||||
path: this.currentFolderId > 0 ? `/${assetPath}/${asset.filename}` : `/${asset.filename}`,
|
||||
text: asset.filename,
|
||||
align: this.imageAlignment
|
||||
})
|
||||
this.activeModal = ''
|
||||
},
|
||||
browse () {
|
||||
this.$refs.pond.browse()
|
||||
},
|
||||
async upload () {
|
||||
const files = this.$refs.pond.getFiles()
|
||||
if (files.length < 1) {
|
||||
return this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:assets.noUploadError'),
|
||||
style: 'warning',
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
for (let file of files) {
|
||||
file.setMetadata({
|
||||
folderId: this.currentFolderId
|
||||
})
|
||||
}
|
||||
await this.$refs.pond.processFiles()
|
||||
},
|
||||
async onFileProcessed (err, file) {
|
||||
if (err) {
|
||||
return this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:assets.uploadFailed'),
|
||||
style: 'error',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
_.delay(() => {
|
||||
this.$refs.pond.removeFile(file.id)
|
||||
}, 5000)
|
||||
|
||||
await this.$apollo.queries.assets.refetch()
|
||||
},
|
||||
downFolder(folder) {
|
||||
this.$store.commit('editor/pushMediaFolderTree', folder)
|
||||
this.currentFolderId = folder.id
|
||||
this.currentFileId = null
|
||||
},
|
||||
upFolder() {
|
||||
this.$store.commit('editor/popMediaFolderTree')
|
||||
const parentFolder = _.last(this.folderTree)
|
||||
this.currentFolderId = parentFolder ? parentFolder.id : 0
|
||||
this.currentFileId = null
|
||||
},
|
||||
async createFolder() {
|
||||
this.$store.commit(`loadingStart`, 'editor-media-createfolder')
|
||||
this.newFolderLoading = true
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: createAssetFolderMutation,
|
||||
variables: {
|
||||
parentFolderId: this.currentFolderId,
|
||||
slug: this.newFolderName
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.assets.createFolder.responseResult.succeeded', false)) {
|
||||
await this.$apollo.queries.folders.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:assets.folderCreateSuccess'),
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
this.newFolderDialog = false
|
||||
this.newFolderName = ''
|
||||
} else {
|
||||
this.$store.commit('pushGraphError', new Error(_.get(resp, 'data.assets.createFolder.responseResult.message')))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
}
|
||||
this.newFolderLoading = false
|
||||
this.$store.commit(`loadingStop`, 'editor-media-createfolder')
|
||||
},
|
||||
openRenameDialog() {
|
||||
this.renameAssetName = this.currentAsset.filename
|
||||
this.renameDialog = true
|
||||
},
|
||||
async renameAsset() {
|
||||
this.$store.commit(`loadingStart`, 'editor-media-renameasset')
|
||||
this.renameAssetLoading = true
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: renameAssetMutation,
|
||||
variables: {
|
||||
id: this.currentFileId,
|
||||
filename: this.renameAssetName
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.assets.renameAsset.responseResult.succeeded', false)) {
|
||||
await this.$apollo.queries.assets.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:assets.renameSuccess'),
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
this.renameDialog = false
|
||||
this.renameAssetName = ''
|
||||
} else {
|
||||
this.$store.commit('pushGraphError', new Error(_.get(resp, 'data.assets.renameAsset.responseResult.message')))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
}
|
||||
this.renameAssetLoading = false
|
||||
this.$store.commit(`loadingStop`, 'editor-media-renameasset')
|
||||
},
|
||||
async deleteAsset() {
|
||||
this.$store.commit(`loadingStart`, 'editor-media-deleteasset')
|
||||
this.deleteAssetLoading = true
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: deleteAssetMutation,
|
||||
variables: {
|
||||
id: this.currentFileId
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.assets.deleteAsset.responseResult.succeeded', false)) {
|
||||
this.currentFileId = null
|
||||
await this.$apollo.queries.assets.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('editor:assets.deleteSuccess'),
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
this.deleteDialog = false
|
||||
} else {
|
||||
this.$store.commit('pushGraphError', new Error(_.get(resp, 'data.assets.deleteAsset.responseResult.message')))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
}
|
||||
this.deleteAssetLoading = false
|
||||
this.$store.commit(`loadingStop`, 'editor-media-deleteasset')
|
||||
},
|
||||
cancel () {
|
||||
this.activeModal = ''
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
folders: {
|
||||
query: listFolderAssetQuery,
|
||||
variables() {
|
||||
return {
|
||||
parentFolderId: this.currentFolderId
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
update: (data) => data.assets.folders,
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'editor-media-folders-list-refresh')
|
||||
}
|
||||
},
|
||||
assets: {
|
||||
query: listAssetQuery,
|
||||
variables() {
|
||||
return {
|
||||
folderId: this.currentFolderId,
|
||||
kind: 'ALL'
|
||||
}
|
||||
},
|
||||
throttle: 1000,
|
||||
fetchPolicy: 'network-only',
|
||||
update: (data) => data.assets.list,
|
||||
watchLoading (isLoading) {
|
||||
this.loading = isLoading
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'editor-media-list-refresh')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.editor-modal-media {
|
||||
position: fixed !important;
|
||||
top: 112px;
|
||||
left: 64px;
|
||||
z-index: 10;
|
||||
width: calc(100vw - 64px - 17px);
|
||||
height: calc(100vh - 112px - 24px);
|
||||
background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important;
|
||||
overflow: auto;
|
||||
|
||||
@include until($tablet) {
|
||||
left: 40px;
|
||||
width: calc(100vw - 40px);
|
||||
height: calc(100vh - 112px - 24px);
|
||||
}
|
||||
|
||||
&.is-editor-ckeditor {
|
||||
top: 64px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 64px - 26px);
|
||||
|
||||
@include until($tablet) {
|
||||
top: 56px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 56px - 24px);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-editor-code {
|
||||
top: 64px;
|
||||
height: calc(100vh - 64px - 26px);
|
||||
|
||||
@include until($tablet) {
|
||||
top: 56px;
|
||||
height: calc(100vh - 56px - 24px);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-editor-common {
|
||||
top: 64px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 64px);
|
||||
|
||||
@include until($tablet) {
|
||||
top: 56px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 56px);
|
||||
}
|
||||
}
|
||||
|
||||
.filepond--root {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filepond--drop-label {
|
||||
cursor: pointer;
|
||||
|
||||
> label {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.filepond--file-action-button.filepond--action-process-item {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v-btn--icon {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,447 +0,0 @@
|
||||
<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>
|
@ -1,40 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-dialog(v-model='isShown', max-width='550')
|
||||
v-card
|
||||
.dialog-header.is-short.is-red
|
||||
v-icon.mr-2(color='white') mdi-alert
|
||||
span {{$t('editor:unsaved.title')}}
|
||||
v-card-text.pt-4
|
||||
.body-2 {{$t('editor:unsaved.body')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(text, @click='isShown = false') {{$t('common:actions.cancel')}}
|
||||
v-btn.px-4(color='red', @click='discard', dark) {{$t('common:actions.discardChanges')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return { }
|
||||
},
|
||||
computed: {
|
||||
isShown: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async discard() {
|
||||
this.isShown = false
|
||||
this.$emit('discard', true)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,224 +0,0 @@
|
||||
<template lang='pug'>
|
||||
.editor-redirect
|
||||
.editor-redirect-main
|
||||
.editor-redirect-editor
|
||||
v-container.px-2.pt-1(fluid)
|
||||
v-row(dense)
|
||||
v-col(
|
||||
cols='12'
|
||||
lg='8'
|
||||
offset-lg='2'
|
||||
xl='6'
|
||||
offset-xl='3'
|
||||
)
|
||||
v-card.pt-2
|
||||
v-card-text
|
||||
.pb-1
|
||||
.subtitle-2.primary--text When a user reaches this page
|
||||
.caption.grey--text.text--darken-1 and matches one of these rules...
|
||||
v-timeline(dense)
|
||||
v-slide-x-reverse-transition(group, hide-on-leave)
|
||||
v-timeline-item(
|
||||
key='cond-add-new'
|
||||
hide-dot
|
||||
)
|
||||
v-btn(
|
||||
color='primary'
|
||||
@click=''
|
||||
)
|
||||
v-icon(left) mdi-plus
|
||||
span Add Conditional Rule
|
||||
v-timeline-item(
|
||||
key='cond-none'
|
||||
small
|
||||
color='grey'
|
||||
)
|
||||
v-card.grey.lighten-5(flat)
|
||||
v-card-text
|
||||
.body-2: strong No conditional rule
|
||||
em Add conditional rules to direct users to a different page based on their group.
|
||||
v-timeline-item(
|
||||
key='cond-rule-1'
|
||||
small
|
||||
color='primary'
|
||||
)
|
||||
v-card.blue-grey.lighten-5(flat)
|
||||
v-card-text
|
||||
.d-flex.align-center
|
||||
.body-2: strong User is a member of any of these groups:
|
||||
v-select.ml-3(
|
||||
color='primary'
|
||||
:items='groups'
|
||||
item-text='name'
|
||||
item-value='id'
|
||||
multiple
|
||||
solo
|
||||
flat
|
||||
hide-details
|
||||
dense
|
||||
chips
|
||||
small-chips
|
||||
)
|
||||
v-divider.my-3
|
||||
.d-flex.align-center
|
||||
.body-2.mr-3 then redirect to
|
||||
v-btn-toggle.mr-3(
|
||||
v-model='fallbackMode'
|
||||
mandatory
|
||||
color='primary'
|
||||
borderless
|
||||
dense
|
||||
)
|
||||
v-btn.text-none(value='page') Page
|
||||
v-btn.text-none(value='url') External URL
|
||||
v-btn.mr-3(
|
||||
v-if='fallbackMode === `page`'
|
||||
color='primary'
|
||||
)
|
||||
v-icon(left) mdi-magnify
|
||||
span Select Page...
|
||||
v-text-field(
|
||||
v-if='fallbackMode === `url`'
|
||||
label='External URL'
|
||||
outlined
|
||||
hint='Required - Title of the API'
|
||||
hide-details
|
||||
v-model='fallbackUrl'
|
||||
dense
|
||||
single-line
|
||||
)
|
||||
v-divider.mb-5
|
||||
.subtitle-2.primary--text Otherwise, redirect to...
|
||||
.caption.grey--text.text--darken-1.pb-2 This fallback rule is mandatory and used if none of the conditional rules above applies.
|
||||
.d-flex.align-center
|
||||
v-btn-toggle.mr-3(
|
||||
v-model='fallbackMode'
|
||||
mandatory
|
||||
color='primary'
|
||||
borderless
|
||||
dense
|
||||
)
|
||||
v-btn.text-none(value='page') Page
|
||||
v-btn.text-none(value='url') External URL
|
||||
v-btn.mr-3(
|
||||
v-if='fallbackMode === `page`'
|
||||
color='primary'
|
||||
)
|
||||
v-icon(left) mdi-magnify
|
||||
span Select Page...
|
||||
v-text-field(
|
||||
v-if='fallbackMode === `url`'
|
||||
label='External URL'
|
||||
outlined
|
||||
hint='Required - Title of the API'
|
||||
hide-details
|
||||
v-model='fallbackUrl'
|
||||
dense
|
||||
single-line
|
||||
)
|
||||
|
||||
v-system-bar.editor-redirect-sysbar(dark, status, color='grey darken-3')
|
||||
.caption.editor-redirect-sysbar-locale {{locale.toUpperCase()}}
|
||||
.caption.px-3 /{{path}}
|
||||
template(v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-spacer
|
||||
.caption Redirect
|
||||
v-spacer
|
||||
.caption 0 rules
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fallbackMode: 'page',
|
||||
fallbackUrl: 'https://'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$vuetify.breakpoint.smAndDown
|
||||
},
|
||||
locale: get('page/locale'),
|
||||
path: get('page/path'),
|
||||
mode: get('editor/mode'),
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
mounted() {
|
||||
this.$store.set('editor/editorKey', 'redirect')
|
||||
|
||||
if (this.mode === 'create') {
|
||||
this.$store.set('editor/content', '<h1>Title</h1>\n\n<p>Some text here</p>')
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
groups: {
|
||||
query: gql`
|
||||
{
|
||||
groups {
|
||||
list {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'network-only',
|
||||
update: (data) => data.groups.list,
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'editor-redirect-groups')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
$editor-height: calc(100vh - 64px - 24px);
|
||||
$editor-height-mobile: calc(100vh - 56px - 16px);
|
||||
|
||||
.editor-redirect {
|
||||
&-main {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-editor {
|
||||
background-color: darken(mc('grey', '100'), 4.5%);
|
||||
flex: 1 1 50%;
|
||||
display: block;
|
||||
height: $editor-height;
|
||||
position: relative;
|
||||
|
||||
@at-root .theme--dark & {
|
||||
background-color: darken(mc('grey', '900'), 4.5%);
|
||||
}
|
||||
}
|
||||
|
||||
&-sidebar {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
&-sysbar {
|
||||
padding-left: 0 !important;
|
||||
|
||||
&-locale {
|
||||
background-color: rgba(255,255,255,.25);
|
||||
display:inline-flex;
|
||||
padding: 0 12px;
|
||||
height: 24px;
|
||||
width: 63px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@ -1,62 +0,0 @@
|
||||
// Header matching code by CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
import CodeMirror from 'codemirror'
|
||||
|
||||
const maxDepth = 100
|
||||
const codeBlockStartMatch = /^`{3}[a-zA-Z0-9]+$/
|
||||
const codeBlockEndMatch = /^`{3}$/
|
||||
|
||||
CodeMirror.registerHelper('fold', 'markdown', function (cm, start) {
|
||||
const firstLine = cm.getLine(start.line)
|
||||
const lastLineNo = cm.lastLine()
|
||||
let end
|
||||
|
||||
function isHeader(lineNo) {
|
||||
const tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0))
|
||||
return tokentype && /\bheader\b/.test(tokentype)
|
||||
}
|
||||
|
||||
function headerLevel(lineNo, line, nextLine) {
|
||||
let match = line && line.match(/^#+/)
|
||||
if (match && isHeader(lineNo)) return match[0].length
|
||||
match = nextLine && nextLine.match(/^[=-]+\s*$/)
|
||||
if (match && isHeader(lineNo + 1)) return nextLine[0] === '=' ? 1 : 2
|
||||
return maxDepth
|
||||
}
|
||||
|
||||
// -> CODE BLOCK
|
||||
|
||||
if (codeBlockStartMatch.test(cm.getLine(start.line))) {
|
||||
end = start.line
|
||||
let nextNextLine = cm.getLine(end + 1)
|
||||
while (end < lastLineNo) {
|
||||
if (codeBlockEndMatch.test(nextNextLine)) {
|
||||
end++
|
||||
break
|
||||
}
|
||||
end++
|
||||
nextNextLine = cm.getLine(end + 1)
|
||||
}
|
||||
} else {
|
||||
// -> HEADER
|
||||
|
||||
let nextLine = cm.getLine(start.line + 1)
|
||||
const level = headerLevel(start.line, firstLine, nextLine)
|
||||
if (level === maxDepth) return undefined
|
||||
|
||||
end = start.line
|
||||
let nextNextLine = cm.getLine(end + 2)
|
||||
while (end < lastLineNo) {
|
||||
if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break
|
||||
++end
|
||||
nextLine = nextNextLine
|
||||
nextNextLine = cm.getLine(end + 2)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
from: CodeMirror.Pos(start.line, firstLine.length),
|
||||
to: CodeMirror.Pos(end, cm.getLine(end).length)
|
||||
}
|
||||
})
|
@ -1,352 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-card.editor-markdown-help.animated.fadeInLeft(flat, tile)
|
||||
v-container.pa-3(grid-list-lg, fluid)
|
||||
v-layout(row, wrap)
|
||||
v-flex(xs12, lg6, xl4)
|
||||
v-card.radius-7.animated.fadeInUp(light)
|
||||
v-card-text
|
||||
.d-flex
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat, height='44')
|
||||
v-icon.mr-3(color='teal') mdi-information-variant
|
||||
.body-2.teal--text Markdown Reference
|
||||
.body-2.mt-3 Bold
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div **Lorem ipsum**
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption: strong Lorem ipsum
|
||||
.body-2.mt-3 Italic
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div *Lorem ipsum*
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption: em Lorem ipsum
|
||||
.body-2.mt-3 Strikethrough
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div ~~Lorem ipsum~~
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption(style='text-decoration: line-through;') Lorem ipsum
|
||||
.body-2.mt-3 Headers
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div # Header 1
|
||||
div ## Header 2
|
||||
div ### Header 3
|
||||
div #### Header 4
|
||||
div ##### Header 5
|
||||
div ###### Header 6
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
div(style='font-weight: 700; font-size: 24px;') Header 1
|
||||
div(style='font-weight: 700; font-size: 22px;') Header 2
|
||||
div(style='font-weight: 700; font-size: 20px;') Header 3
|
||||
div(style='font-weight: 700; font-size: 18px;') Header 4
|
||||
div(style='font-weight: 700; font-size: 16px;') Header 5
|
||||
div(style='font-weight: 700; font-size: 14px;') Header 6
|
||||
.body-2.mt-3 Unordered Lists
|
||||
.caption.grey--text.text--darken-1: em You can also use the asterisk symbol instead of the dash.
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div - Unordered List Item 1
|
||||
div - Unordered List Item 2
|
||||
div - Unordered List Item 3
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
ul
|
||||
li Unordered List Item 1
|
||||
li Unordered List Item 2
|
||||
li Unordered List Item 3
|
||||
.body-2.mt-3 Ordered Lists
|
||||
.caption.grey--text.text--darken-1: em Even though we prefix all lines with #[strong 1.], the output will be correctly numbered automatically.
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div 1. Ordered List Item 1
|
||||
div 1. Ordered List Item 2
|
||||
div 1. Ordered List Item 3
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
ol
|
||||
li Unordered List Item 1
|
||||
li Unordered List Item 2
|
||||
li Unordered List Item 3
|
||||
.body-2.mt-3 Images
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div 
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
img(src='https://via.placeholder.com/150x50.png')
|
||||
v-flex(xs12, lg6, xl4)
|
||||
v-card.radius-7.animated.fadeInUp.wait-p1s(light)
|
||||
v-card-text
|
||||
.d-flex
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat, height='44')
|
||||
v-icon.mr-3(color='teal') mdi-information-variant
|
||||
.body-2.teal--text Markdown Reference (continued)
|
||||
.body-2.mt-3 Links
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div [Link Text](https://wiki.js.org)
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption: a(href='https://wiki.js.org', target='_blank') Link Text
|
||||
.body-2.mt-3 Superscript
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem ^ipsum^
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption Lorem #[sup ipsum]
|
||||
.body-2.mt-3 Subscript
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem ~ipsum~
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption: em Lorem #[sub ipsum]
|
||||
.body-2.mt-3 Horizontal Line
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem ipsum
|
||||
div ---
|
||||
div Dolor sit amet
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption Lorem ipsum
|
||||
v-divider.my-2
|
||||
.caption Dolor sit amet
|
||||
.body-2.mt-3 Inline Code
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem `ipsum dolor sit` amet
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption Lorem #[code ipsum dolor sit] amet
|
||||
.body-2.mt-3 Code Blocks
|
||||
.caption.grey--text.text--darken-1: em In the example below, #[strong js] defines the syntax highlighting language to use. It can be omitted.
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div ```js
|
||||
div function main () {
|
||||
div.pl-3 echo 'Lorem ipsum'
|
||||
div }
|
||||
div ```
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text.contents
|
||||
pre.prismjs.line-numbers.language-js
|
||||
code.language-js
|
||||
span.token.keyword function
|
||||
span.token.function main
|
||||
span.token.punctuation (
|
||||
span.token.punctuation )
|
||||
span.token.punctuation {#[br]
|
||||
| echo
|
||||
span.token.string 'Lorem ipsum'#[br]
|
||||
span.token.punctuation }
|
||||
span.line-numbers-rows(aria-hidden='true')
|
||||
span
|
||||
span
|
||||
span
|
||||
.body-2.mt-3 Blockquotes
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div > Lorem ipsum
|
||||
div > dolor sit amet
|
||||
div > consectetur adipiscing elit
|
||||
v-icon mdi-chevron-right
|
||||
v-flex
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
blockquote(style='border: 1px solid #263238; border-radius: .5rem; padding: 1rem 24px;') Lorem ipsum#[br]dolor sit amet#[br]consectetur adipiscing elit
|
||||
|
||||
v-flex(xs12, xl4)
|
||||
v-card.radius-7.animated.fadeInUp.wait-p2s(light)
|
||||
v-card-text
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat)
|
||||
v-icon.mr-3(color='teal') mdi-keyboard
|
||||
.body-2.teal--text Keyboard Shortcuts
|
||||
v-list.editor-markdown-help-kbd(two-line, dense)
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Bold
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd B]
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Italic
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd I]
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Increase Header Level
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Right]
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Decrease Header Level
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Left]
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Save
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd S]
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Undo
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd Z]
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Redo
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd Y]
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content
|
||||
v-list-item-title.body-2 Distraction Free Mode
|
||||
v-list-item-subtitle Press <kbd>Esc</kbd> to exit.
|
||||
v-list-item-action #[kbd F11]
|
||||
|
||||
v-card.radius-7.animated.fadeInUp.wait-p3s.mt-3(light)
|
||||
v-card-text
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat)
|
||||
v-icon.mr-3(color='teal') mdi-mouse
|
||||
.body-2.teal--text Multi-Selection
|
||||
v-list.editor-markdown-help-kbd(two-line, dense)
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Multiple Cursors
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + Left Click
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Select Region
|
||||
v-list-item-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + Left Click
|
||||
v-divider
|
||||
v-list-item
|
||||
v-list-item-content.body-2 Deselect
|
||||
v-list-item-action #[kbd Esc]
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
ctrlKey() { return /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl' },
|
||||
altKey() { return /Mac/.test(navigator.platform) ? 'Option' : 'Alt' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.editor-markdown-help {
|
||||
position: fixed !important;
|
||||
top: 112px;
|
||||
left: 64px;
|
||||
z-index: 10;
|
||||
width: calc(100vw - 64px - 17px);
|
||||
height: calc(100vh - 112px - 24px);
|
||||
background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important;
|
||||
overflow: auto;
|
||||
|
||||
&-source {
|
||||
background-color: mc('blue-grey', '900') !important;
|
||||
border-radius: 7px;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-size: 14px;
|
||||
color: #FFF !important;
|
||||
|
||||
.v-card__text {
|
||||
color: #FFF !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-result {
|
||||
background-color: mc('blue-grey', '50') !important;
|
||||
border-radius: 7px;
|
||||
font-size: 14px;
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
background-color: mc('pink', '50');
|
||||
box-shadow: none;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.contents {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.prismjs {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-kbd {
|
||||
.v-list-item__action {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
kbd {
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 0.1em 0.5em;
|
||||
margin: 0 0.2em;
|
||||
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff inset;
|
||||
background-color: #f7f7f7;
|
||||
color: mc('grey', '700');
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,190 +0,0 @@
|
||||
const pako = require('pako')
|
||||
|
||||
// ------------------------------------
|
||||
// Markdown - PlantUML Preprocessor
|
||||
// ------------------------------------
|
||||
|
||||
module.exports = {
|
||||
init (mdinst, conf) {
|
||||
mdinst.use((md, opts) => {
|
||||
const openMarker = opts.openMarker || '```plantuml'
|
||||
const openChar = openMarker.charCodeAt(0)
|
||||
const closeMarker = opts.closeMarker || '```'
|
||||
const closeChar = closeMarker.charCodeAt(0)
|
||||
const imageFormat = opts.imageFormat || 'svg'
|
||||
const server = opts.server || 'https://plantuml.requarks.io'
|
||||
|
||||
md.block.ruler.before('fence', 'uml_diagram', (state, startLine, endLine, silent) => {
|
||||
let nextLine
|
||||
let markup
|
||||
let params
|
||||
let token
|
||||
let i
|
||||
let autoClosed = false
|
||||
let start = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
|
||||
// Check out the first character quickly,
|
||||
// this should filter out most of non-uml blocks
|
||||
//
|
||||
if (openChar !== state.src.charCodeAt(start)) { return false }
|
||||
|
||||
// Check out the rest of the marker string
|
||||
//
|
||||
for (i = 0; i < openMarker.length; ++i) {
|
||||
if (openMarker[i] !== state.src[start + i]) { return false }
|
||||
}
|
||||
|
||||
markup = state.src.slice(start, start + i)
|
||||
params = state.src.slice(start + i, max)
|
||||
|
||||
// Since start is found, we can report success here in validation mode
|
||||
//
|
||||
if (silent) { return true }
|
||||
|
||||
// Search for the end of the block
|
||||
//
|
||||
nextLine = startLine
|
||||
|
||||
for (;;) {
|
||||
nextLine++
|
||||
if (nextLine >= endLine) {
|
||||
// unclosed block should be autoclosed by end of document.
|
||||
// also block seems to be autoclosed by end of parent
|
||||
break
|
||||
}
|
||||
|
||||
start = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
max = state.eMarks[nextLine]
|
||||
|
||||
if (start < max && state.sCount[nextLine] < state.blkIndent) {
|
||||
// non-empty line with negative indent should stop the list:
|
||||
// - ```
|
||||
// test
|
||||
break
|
||||
}
|
||||
|
||||
if (closeChar !== state.src.charCodeAt(start)) {
|
||||
// didn't find the closing fence
|
||||
continue
|
||||
}
|
||||
|
||||
if (state.sCount[nextLine] > state.sCount[startLine]) {
|
||||
// closing fence should not be indented with respect of opening fence
|
||||
continue
|
||||
}
|
||||
|
||||
var closeMarkerMatched = true
|
||||
for (i = 0; i < closeMarker.length; ++i) {
|
||||
if (closeMarker[i] !== state.src[start + i]) {
|
||||
closeMarkerMatched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!closeMarkerMatched) {
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure tail has spaces only
|
||||
if (state.skipSpaces(start + i) < max) {
|
||||
continue
|
||||
}
|
||||
|
||||
// found!
|
||||
autoClosed = true
|
||||
break
|
||||
}
|
||||
|
||||
const contents = state.src
|
||||
.split('\n')
|
||||
.slice(startLine + 1, nextLine)
|
||||
.join('\n')
|
||||
|
||||
// We generate a token list for the alt property, to mimic what the image parser does.
|
||||
let altToken = []
|
||||
// Remove leading space if any.
|
||||
let alt = params ? params.slice(1) : 'uml diagram'
|
||||
state.md.inline.parse(
|
||||
alt,
|
||||
state.md,
|
||||
state.env,
|
||||
altToken
|
||||
)
|
||||
|
||||
var zippedCode = encode64(pako.deflate('@startuml\n' + contents + '\n@enduml', { to: 'string' }))
|
||||
|
||||
token = state.push('uml_diagram', 'img', 0)
|
||||
// alt is constructed from children. No point in populating it here.
|
||||
token.attrs = [ [ 'src', `${server}/${imageFormat}/${zippedCode}` ], [ 'alt', '' ], ['class', 'uml-diagram'] ]
|
||||
token.block = true
|
||||
token.children = altToken
|
||||
token.info = params
|
||||
token.map = [ startLine, nextLine ]
|
||||
token.markup = markup
|
||||
|
||||
state.line = nextLine + (autoClosed ? 1 : 0)
|
||||
|
||||
return true
|
||||
}, {
|
||||
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
|
||||
})
|
||||
md.renderer.rules.uml_diagram = md.renderer.rules.image
|
||||
}, {
|
||||
openMarker: conf.openMarker,
|
||||
closeMarker: conf.closeMarker,
|
||||
imageFormat: conf.imageFormat,
|
||||
server: conf.server
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function encode64 (data) {
|
||||
let r = ''
|
||||
for (let i = 0; i < data.length; i += 3) {
|
||||
if (i + 2 === data.length) {
|
||||
r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1), 0)
|
||||
} else if (i + 1 === data.length) {
|
||||
r += append3bytes(data.charCodeAt(i), 0, 0)
|
||||
} else {
|
||||
r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1), data.charCodeAt(i + 2))
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
function append3bytes (b1, b2, b3) {
|
||||
let c1 = b1 >> 2
|
||||
let c2 = ((b1 & 0x3) << 4) | (b2 >> 4)
|
||||
let c3 = ((b2 & 0xF) << 2) | (b3 >> 6)
|
||||
let c4 = b3 & 0x3F
|
||||
let r = ''
|
||||
r += encode6bit(c1 & 0x3F)
|
||||
r += encode6bit(c2 & 0x3F)
|
||||
r += encode6bit(c3 & 0x3F)
|
||||
r += encode6bit(c4 & 0x3F)
|
||||
return r
|
||||
}
|
||||
|
||||
function encode6bit(raw) {
|
||||
let b = raw
|
||||
if (b < 10) {
|
||||
return String.fromCharCode(48 + b)
|
||||
}
|
||||
b -= 10
|
||||
if (b < 26) {
|
||||
return String.fromCharCode(65 + b)
|
||||
}
|
||||
b -= 26
|
||||
if (b < 26) {
|
||||
return String.fromCharCode(97 + b)
|
||||
}
|
||||
b -= 26
|
||||
if (b === 0) {
|
||||
return '-'
|
||||
}
|
||||
if (b === 1) {
|
||||
return '_'
|
||||
}
|
||||
return '?'
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import cash from 'cash-dom'
|
||||
import _ from 'lodash'
|
||||
|
||||
export default {
|
||||
format () {
|
||||
for (let i = 1; i < 6; i++) {
|
||||
cash(`.editor-markdown-preview-content h${i}.tabset`).each((idx, elm) => {
|
||||
elm.innerHTML = 'Tabset ( rendered upon saving )'
|
||||
cash(elm).nextUntil(_.times(i, t => `h${t + 1}`).join(', '), `h${i + 1}`).each((hidx, hd) => {
|
||||
hd.classList.add('tabset-header')
|
||||
cash(hd).nextUntil(_.times(i + 1, t => `h${t + 1}`).join(', ')).wrapAll('<div class="tabset-content"></div>')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,577 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-app(:dark='$vuetify.theme.dark').history
|
||||
nav-header
|
||||
v-content
|
||||
v-toolbar(color='primary', dark)
|
||||
.subheading Viewing history of #[strong /{{path}}]
|
||||
template(v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-spacer
|
||||
.caption.blue--text.text--lighten-3.mr-4 Trail Length: {{total}}
|
||||
.caption.blue--text.text--lighten-3 ID: {{pageId}}
|
||||
v-btn.ml-4(depressed, color='blue darken-1', @click='goLive') Return to Live Version
|
||||
v-container(fluid, grid-list-xl)
|
||||
v-layout(row, wrap)
|
||||
v-flex(xs12, md4)
|
||||
v-chip.my-0.ml-6(
|
||||
label
|
||||
small
|
||||
:color='$vuetify.theme.dark ? `grey darken-2` : `grey lighten-2`'
|
||||
:class='$vuetify.theme.dark ? `grey--text text--lighten-2` : `grey--text text--darken-2`'
|
||||
)
|
||||
span Live
|
||||
v-timeline(
|
||||
dense
|
||||
)
|
||||
v-timeline-item.pb-2(
|
||||
v-for='(ph, idx) in fullTrail'
|
||||
:key='ph.versionId'
|
||||
:small='ph.actionType === `edit`'
|
||||
:color='trailColor(ph.actionType)'
|
||||
:icon='trailIcon(ph.actionType)'
|
||||
)
|
||||
v-card.radius-7(flat, :class='trailBgColor(ph.actionType)')
|
||||
v-toolbar(flat, :color='trailBgColor(ph.actionType)', height='40')
|
||||
.caption(:title='$options.filters.moment(ph.versionDate, `LLL`)') {{ ph.versionDate | moment('ll') }}
|
||||
v-divider.mx-3(vertical)
|
||||
.caption(v-if='ph.actionType === `edit`') Edited by #[strong {{ ph.authorName }}]
|
||||
.caption(v-else-if='ph.actionType === `move`') Moved from #[strong {{ph.valueBefore}}] to #[strong {{ph.valueAfter}}] by #[strong {{ ph.authorName }}]
|
||||
.caption(v-else-if='ph.actionType === `initial`') Created by #[strong {{ ph.authorName }}]
|
||||
.caption(v-else-if='ph.actionType === `live`') Last Edited by #[strong {{ ph.authorName }}]
|
||||
.caption(v-else) Unknown Action by #[strong {{ ph.authorName }}]
|
||||
v-spacer
|
||||
v-menu(offset-x, left)
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.mr-2.radius-4(icon, v-on='on', small, tile): v-icon mdi-dots-horizontal
|
||||
v-list(dense, nav).history-promptmenu
|
||||
v-list-item(@click='setDiffSource(ph.versionId)', :disabled='(ph.versionId >= diffTarget && diffTarget !== 0) || ph.versionId === 0')
|
||||
v-list-item-avatar(size='24'): v-avatar A
|
||||
v-list-item-title Set as Differencing Source
|
||||
v-list-item(@click='setDiffTarget(ph.versionId)', :disabled='ph.versionId <= diffSource && ph.versionId !== 0')
|
||||
v-list-item-avatar(size='24'): v-avatar B
|
||||
v-list-item-title Set as Differencing Target
|
||||
v-list-item(@click='viewSource(ph.versionId)')
|
||||
v-list-item-avatar(size='24'): v-icon mdi-code-tags
|
||||
v-list-item-title View Source
|
||||
v-list-item(@click='download(ph.versionId)')
|
||||
v-list-item-avatar(size='24'): v-icon mdi-cloud-download-outline
|
||||
v-list-item-title Download Version
|
||||
v-list-item(@click='restore(ph.versionId, ph.versionDate)', :disabled='ph.versionId === 0')
|
||||
v-list-item-avatar(size='24'): v-icon(:disabled='ph.versionId === 0') mdi-history
|
||||
v-list-item-title Restore
|
||||
v-list-item(@click='branchOff(ph.versionId)')
|
||||
v-list-item-avatar(size='24'): v-icon mdi-source-branch
|
||||
v-list-item-title Branch off from here
|
||||
v-btn.mr-2.radius-4(
|
||||
@click='setDiffSource(ph.versionId)'
|
||||
icon
|
||||
small
|
||||
depressed
|
||||
tile
|
||||
:class='diffSource === ph.versionId ? `pink white--text` : ($vuetify.theme.dark ? `grey darken-2` : `grey lighten-2`)'
|
||||
:disabled='(ph.versionId >= diffTarget && diffTarget !== 0) || ph.versionId === 0'
|
||||
): strong A
|
||||
v-btn.mr-0.radius-4(
|
||||
@click='setDiffTarget(ph.versionId)'
|
||||
icon
|
||||
small
|
||||
depressed
|
||||
tile
|
||||
:class='diffTarget === ph.versionId ? `pink white--text` : ($vuetify.theme.dark ? `grey darken-2` : `grey lighten-2`)'
|
||||
:disabled='ph.versionId <= diffSource && ph.versionId !== 0'
|
||||
): strong B
|
||||
|
||||
v-btn.ma-0.radius-7(
|
||||
v-if='total > trail.length'
|
||||
block
|
||||
color='primary'
|
||||
@click='loadMore'
|
||||
)
|
||||
.caption.white--text Load More...
|
||||
|
||||
v-chip.ma-0(
|
||||
v-else
|
||||
label
|
||||
small
|
||||
:color='$vuetify.theme.dark ? `grey darken-2` : `grey lighten-2`'
|
||||
:class='$vuetify.theme.dark ? `grey--text text--lighten-2` : `grey--text text--darken-2`'
|
||||
) End of history trail
|
||||
|
||||
v-flex(xs12, md8)
|
||||
v-card.radius-7(:class='$vuetify.breakpoint.mdAndUp ? `mt-8` : ``')
|
||||
v-card-text
|
||||
v-card.grey.radius-7(flat, :class='$vuetify.theme.dark ? `darken-2` : `lighten-4`')
|
||||
v-row(no-gutters, align='center')
|
||||
v-col
|
||||
v-card-text
|
||||
.subheading {{target.title}}
|
||||
.caption {{target.description}}
|
||||
v-col.text-right.py-3(cols='2', v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-btn.mr-3(:color='$vuetify.theme.dark ? `white` : `grey darken-3`', small, dark, outlined, @click='toggleViewMode')
|
||||
v-icon(left) mdi-eye
|
||||
.overline View Mode
|
||||
v-card.mt-3(light, v-html='diffHTML', flat)
|
||||
|
||||
v-dialog(v-model='isRestoreConfirmDialogShown', max-width='650', persistent)
|
||||
v-card
|
||||
.dialog-header.is-orange {{$t('history:restore.confirmTitle')}}
|
||||
v-card-text.pa-4
|
||||
i18next(tag='span', path='history:restore.confirmText')
|
||||
strong(place='date') {{ restoreTarget.versionDate | moment('LLL') }}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(text, @click='isRestoreConfirmDialogShown = false', :disabled='restoreLoading') {{$t('common:actions.cancel')}}
|
||||
v-btn(color='orange darken-2', dark, @click='restoreConfirm', :loading='restoreLoading') {{$t('history:restore.confirmButton')}}
|
||||
|
||||
page-selector(mode='create', v-model='branchOffOpts.modal', :open-handler='branchOffHandle', :path='branchOffOpts.path', :locale='branchOffOpts.locale')
|
||||
|
||||
nav-footer
|
||||
notify
|
||||
search-results
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as Diff2Html from 'diff2html'
|
||||
import { createPatch } from 'diff'
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
i18nOptions: { namespaces: 'history' },
|
||||
props: {
|
||||
pageId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: 'home'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Untitled Page'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
createdAt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
updatedAt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
authorName: {
|
||||
type: String,
|
||||
default: 'Unknown'
|
||||
},
|
||||
authorId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isPublished: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
liveContent: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
effectivePermissions: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
source: {
|
||||
versionId: 0,
|
||||
content: '',
|
||||
title: '',
|
||||
description: ''
|
||||
},
|
||||
target: {
|
||||
versionId: 0,
|
||||
content: '',
|
||||
title: '',
|
||||
description: ''
|
||||
},
|
||||
trail: [],
|
||||
diffSource: 0,
|
||||
diffTarget: 0,
|
||||
offsetPage: 0,
|
||||
total: 0,
|
||||
viewMode: 'line-by-line',
|
||||
cache: [],
|
||||
restoreTarget: {
|
||||
versionId: 0,
|
||||
versionDate: ''
|
||||
},
|
||||
branchOffOpts: {
|
||||
versionId: 0,
|
||||
locale: 'en',
|
||||
path: 'new-page',
|
||||
modal: false
|
||||
},
|
||||
isRestoreConfirmDialogShown: false,
|
||||
restoreLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fullTrail () {
|
||||
const liveTrailItem = {
|
||||
versionId: 0,
|
||||
authorId: this.authorId,
|
||||
authorName: this.authorName,
|
||||
actionType: 'live',
|
||||
valueBefore: null,
|
||||
valueAfter: null,
|
||||
versionDate: this.updatedAt
|
||||
}
|
||||
// -> Check for move between latest and live
|
||||
const prevPage = _.find(this.cache, ['versionId', _.get(this.trail, '[0].versionId', -1)])
|
||||
if (prevPage && this.path !== prevPage.path) {
|
||||
liveTrailItem.actionType = 'move'
|
||||
liveTrailItem.valueBefore = prevPage.path
|
||||
liveTrailItem.valueAfter = this.path
|
||||
}
|
||||
// -> Combine trail with live
|
||||
return [
|
||||
liveTrailItem,
|
||||
...this.trail
|
||||
]
|
||||
},
|
||||
diffs () {
|
||||
return createPatch(`/${this.path}`, this.source.content, this.target.content)
|
||||
},
|
||||
diffHTML () {
|
||||
return Diff2Html.html(this.diffs, {
|
||||
inputFormat: 'diff',
|
||||
drawFileList: false,
|
||||
matching: 'lines',
|
||||
outputFormat: this.viewMode
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
trail (newValue, oldValue) {
|
||||
if (newValue && newValue.length > 0) {
|
||||
this.diffTarget = 0
|
||||
this.diffSource = _.get(_.head(newValue), 'versionId', 0)
|
||||
}
|
||||
},
|
||||
async diffSource (newValue, oldValue) {
|
||||
if (this.diffSource !== this.source.versionId) {
|
||||
const page = _.find(this.cache, { versionId: newValue })
|
||||
if (page) {
|
||||
this.source = page
|
||||
} else {
|
||||
this.source = await this.loadVersion(newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
async diffTarget (newValue, oldValue) {
|
||||
if (this.diffTarget !== this.target.versionId) {
|
||||
const page = _.find(this.cache, { versionId: newValue })
|
||||
if (page) {
|
||||
this.target = page
|
||||
} else {
|
||||
this.target = await this.loadVersion(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.commit('page/SET_ID', this.id)
|
||||
this.$store.commit('page/SET_LOCALE', this.locale)
|
||||
this.$store.commit('page/SET_PATH', this.path)
|
||||
|
||||
this.$store.commit('page/SET_MODE', 'history')
|
||||
|
||||
this.cache.push({
|
||||
action: 'live',
|
||||
authorId: this.authorId,
|
||||
authorName: this.authorName,
|
||||
content: this.liveContent,
|
||||
contentType: '',
|
||||
createdAt: this.createdAt,
|
||||
description: this.description,
|
||||
editor: '',
|
||||
isPrivate: false,
|
||||
isPublished: this.isPublished,
|
||||
locale: this.locale,
|
||||
pageId: this.pageId,
|
||||
path: this.path,
|
||||
publishEndDate: '',
|
||||
publishStartDate: '',
|
||||
tags: this.tags,
|
||||
title: this.title,
|
||||
versionId: 0,
|
||||
versionDate: this.updatedAt
|
||||
})
|
||||
|
||||
this.target = this.cache[0]
|
||||
|
||||
if (this.effectivePermissions) {
|
||||
this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString()))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadVersion (versionId) {
|
||||
this.$store.commit(`loadingStart`, 'history-version-' + versionId)
|
||||
const resp = await this.$apollo.query({
|
||||
query: gql`
|
||||
query ($pageId: Int!, $versionId: Int!) {
|
||||
pages {
|
||||
version (pageId: $pageId, versionId: $versionId) {
|
||||
action
|
||||
authorId
|
||||
authorName
|
||||
content
|
||||
contentType
|
||||
createdAt
|
||||
versionDate
|
||||
description
|
||||
editor
|
||||
isPrivate
|
||||
isPublished
|
||||
locale
|
||||
pageId
|
||||
path
|
||||
publishEndDate
|
||||
publishStartDate
|
||||
tags
|
||||
title
|
||||
versionId
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
versionId,
|
||||
pageId: this.pageId
|
||||
}
|
||||
})
|
||||
this.$store.commit(`loadingStop`, 'history-version-' + versionId)
|
||||
const page = _.get(resp, 'data.pages.version', null)
|
||||
if (page) {
|
||||
this.cache.push(page)
|
||||
return page
|
||||
} else {
|
||||
return { content: '' }
|
||||
}
|
||||
},
|
||||
viewSource (versionId) {
|
||||
window.location.assign(`/s/${this.locale}/${this.path}?v=${versionId}`)
|
||||
},
|
||||
download (versionId) {
|
||||
window.location.assign(`/d/${this.locale}/${this.path}?v=${versionId}`)
|
||||
},
|
||||
restore (versionId, versionDate) {
|
||||
this.restoreTarget = {
|
||||
versionId,
|
||||
versionDate
|
||||
}
|
||||
this.isRestoreConfirmDialogShown = true
|
||||
},
|
||||
async restoreConfirm () {
|
||||
this.restoreLoading = true
|
||||
this.$store.commit(`loadingStart`, 'history-restore')
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation ($pageId: Int!, $versionId: Int!) {
|
||||
pages {
|
||||
restore (pageId: $pageId, versionId: $versionId) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
versionId: this.restoreTarget.versionId,
|
||||
pageId: this.pageId
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.pages.restore.responseResult.succeeded', false) === true) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'success',
|
||||
message: this.$t('history:restore.success'),
|
||||
icon: 'check'
|
||||
})
|
||||
this.isRestoreConfirmDialogShown = false
|
||||
setTimeout(() => {
|
||||
window.location.assign(`/${this.locale}/${this.path}`)
|
||||
}, 1000)
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.pages.restore.responseResult.message', 'An unexpected error occurred'))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: err.message,
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
this.$store.commit(`loadingStop`, 'history-restore')
|
||||
this.restoreLoading = false
|
||||
},
|
||||
branchOff (versionId) {
|
||||
const pathParts = this.path.split('/')
|
||||
this.branchOffOpts = {
|
||||
versionId: versionId,
|
||||
locale: this.locale,
|
||||
path: (pathParts.length > 1) ? _.initial(pathParts).join('/') + `/new-page` : `new-page`,
|
||||
modal: true
|
||||
}
|
||||
},
|
||||
branchOffHandle ({ locale, path }) {
|
||||
window.location.assign(`/e/${locale}/${path}?from=${this.pageId},${this.branchOffOpts.versionId}`)
|
||||
},
|
||||
toggleViewMode () {
|
||||
this.viewMode = (this.viewMode === 'line-by-line') ? 'side-by-side' : 'line-by-line'
|
||||
},
|
||||
goLive () {
|
||||
window.location.assign(`/${this.path}`)
|
||||
},
|
||||
setDiffSource (versionId) {
|
||||
this.diffSource = versionId
|
||||
},
|
||||
setDiffTarget (versionId) {
|
||||
this.diffTarget = versionId
|
||||
},
|
||||
loadMore () {
|
||||
this.offsetPage++
|
||||
this.$apollo.queries.trail.fetchMore({
|
||||
variables: {
|
||||
id: this.pageId,
|
||||
offsetPage: this.offsetPage,
|
||||
offsetSize: this.$vuetify.breakpoint.mdAndUp ? 25 : 5
|
||||
},
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
return {
|
||||
pages: {
|
||||
history: {
|
||||
total: previousResult.pages.history.total,
|
||||
trail: [...previousResult.pages.history.trail, ...fetchMoreResult.pages.history.trail],
|
||||
__typename: previousResult.pages.history.__typename
|
||||
},
|
||||
__typename: previousResult.pages.__typename
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
trailColor (actionType) {
|
||||
switch (actionType) {
|
||||
case 'edit':
|
||||
return 'primary'
|
||||
case 'move':
|
||||
return 'purple'
|
||||
case 'initial':
|
||||
return 'teal'
|
||||
case 'live':
|
||||
return 'orange'
|
||||
default:
|
||||
return 'grey'
|
||||
}
|
||||
},
|
||||
trailIcon (actionType) {
|
||||
switch (actionType) {
|
||||
case 'edit':
|
||||
return '' // 'mdi-pencil'
|
||||
case 'move':
|
||||
return 'mdi-forward'
|
||||
case 'initial':
|
||||
return 'mdi-plus'
|
||||
case 'live':
|
||||
return 'mdi-atom-variant'
|
||||
default:
|
||||
return 'mdi-alert'
|
||||
}
|
||||
},
|
||||
trailBgColor (actionType) {
|
||||
switch (actionType) {
|
||||
case 'move':
|
||||
return this.$vuetify.theme.dark ? 'purple' : 'purple lighten-5'
|
||||
case 'initial':
|
||||
return this.$vuetify.theme.dark ? 'teal darken-3' : 'teal lighten-5'
|
||||
case 'live':
|
||||
return this.$vuetify.theme.dark ? 'orange darken-3' : 'orange lighten-5'
|
||||
default:
|
||||
return this.$vuetify.theme.dark ? 'grey darken-3' : 'grey lighten-4'
|
||||
}
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
trail: {
|
||||
query: gql`
|
||||
query($id: Int!, $offsetPage: Int, $offsetSize: Int) {
|
||||
pages {
|
||||
history(id:$id, offsetPage:$offsetPage, offsetSize:$offsetSize) {
|
||||
trail {
|
||||
versionId
|
||||
authorId
|
||||
authorName
|
||||
actionType
|
||||
valueBefore
|
||||
valueAfter
|
||||
versionDate
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables () {
|
||||
return {
|
||||
id: this.pageId,
|
||||
offsetPage: 0,
|
||||
offsetSize: this.$vuetify.breakpoint.mdAndUp ? 25 : 5
|
||||
}
|
||||
},
|
||||
manual: true,
|
||||
result ({ data, loading, networkStatus }) {
|
||||
this.total = data.pages.history.total
|
||||
this.trail = data.pages.history.trail
|
||||
},
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'history-trail-refresh')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
.history {
|
||||
&-promptmenu {
|
||||
border-top: 5px solid mc('blue', '700');
|
||||
}
|
||||
|
||||
.d2h-file-wrapper {
|
||||
border: 1px solid #EEE;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.d2h-file-header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -1,37 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-app
|
||||
.newpage
|
||||
.newpage-content
|
||||
img.animated.fadeIn(src='/_assets-legacy/svg/icon-delete-file.svg', alt='Not Found')
|
||||
.headline {{ $t('newpage.title') }}
|
||||
.subtitle-1.mt-3 {{ $t('newpage.subtitle') }}
|
||||
v-btn.mt-5(:href='`/e/` + locale + `/` + path', x-large)
|
||||
v-icon(left) mdi-plus
|
||||
span {{ $t('newpage.create') }}
|
||||
v-btn.mt-5(color='purple lighten-3', href='javascript:window.history.go(-1);', outlined)
|
||||
v-icon(left) mdi-arrow-left
|
||||
span {{ $t('newpage.goback') }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: 'home'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return { }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
@ -1,24 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-app
|
||||
.notfound
|
||||
.notfound-content
|
||||
img.animated.fadeIn(src='/_assets-legacy/svg/icon-delete-file.svg', alt='Not Found')
|
||||
.headline {{$t('notfound.title')}}
|
||||
.subheading.mt-3 {{$t('notfound.subtitle')}}
|
||||
v-btn.mt-5(color='red lighten-4', href='/', large, outlined)
|
||||
v-icon(left) mdi-home
|
||||
span {{$t('notfound.gohome')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return { }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
@ -1,106 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-app(:dark='$vuetify.theme.dark').source
|
||||
nav-header
|
||||
v-content
|
||||
v-toolbar(color='primary', dark)
|
||||
i18next.subheading(v-if='versionId > 0', path='common:page.viewingSourceVersion', tag='div')
|
||||
strong(place='date', :title='$options.filters.moment(versionDate, `LLL`)') {{versionDate | moment('lll')}}
|
||||
strong(place='path') /{{path}}
|
||||
i18next.subheading(v-else, path='common:page.viewingSource', tag='div')
|
||||
strong(place='path') /{{path}}
|
||||
template(v-if='$vuetify.breakpoint.mdAndUp')
|
||||
v-spacer
|
||||
.caption.blue--text.text--lighten-3 {{$t('common:page.id', { id: pageId })}}
|
||||
.caption.blue--text.text--lighten-3.ml-4(v-if='versionId > 0') {{$t('common:page.versionId', { id: versionId })}}
|
||||
v-btn.ml-4(v-if='versionId > 0', depressed, color='blue darken-1', @click='goHistory')
|
||||
v-icon mdi-history
|
||||
v-btn.ml-4(depressed, color='blue darken-1', @click='goLive') {{$t('common:page.returnNormalView')}}
|
||||
v-card(tile)
|
||||
v-card-text
|
||||
v-card.grey.radius-7(flat, :class='$vuetify.theme.dark ? `darken-4` : `lighten-4`')
|
||||
v-card-text
|
||||
pre
|
||||
code
|
||||
slot
|
||||
|
||||
nav-footer
|
||||
notify
|
||||
search-results
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
pageId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: 'home'
|
||||
},
|
||||
versionId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
versionDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
effectivePermissions: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created () {
|
||||
this.$store.commit('page/SET_ID', this.id)
|
||||
this.$store.commit('page/SET_LOCALE', this.locale)
|
||||
this.$store.commit('page/SET_PATH', this.path)
|
||||
|
||||
this.$store.commit('page/SET_MODE', 'source')
|
||||
|
||||
if (this.effectivePermissions) {
|
||||
this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString()))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goLive() {
|
||||
window.location.assign(`/${this.locale}/${this.path}`)
|
||||
},
|
||||
goHistory () {
|
||||
window.location.assign(`/h/${this.locale}/${this.path}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
.source {
|
||||
pre > code {
|
||||
box-shadow: none;
|
||||
background-color: transparent;
|
||||
color: mc('grey', '800');
|
||||
font-family: 'Roboto Mono', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
|
||||
@at-root .theme--dark.source pre > code {
|
||||
background-color: mc('grey', '900');
|
||||
color: mc('grey', '400');
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -1,341 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-app(:dark='$vuetify.theme.dark').tags
|
||||
nav-header
|
||||
v-navigation-drawer.pb-0.elevation-1(app, fixed, clipped, :right='$vuetify.rtl', permanent, width='300')
|
||||
vue-scroll(:ops='scrollStyle')
|
||||
v-list(dense, nav)
|
||||
v-list-item(href='/')
|
||||
v-list-item-icon: v-icon mdi-home
|
||||
v-list-item-title {{$t('common:header.home')}}
|
||||
template(v-for='(tags, groupName) in tagsGrouped')
|
||||
v-divider.my-2
|
||||
v-subheader.pl-4(:key='`tagGroup-` + groupName') {{groupName}}
|
||||
v-list-item(v-for='tag of tags', @click='toggleTag(tag.tag)', :key='`tag-` + tag.tag')
|
||||
v-list-item-icon
|
||||
v-icon(v-if='isSelected(tag.tag)', color='primary') mdi-checkbox-intermediate
|
||||
v-icon(v-else) mdi-checkbox-blank-outline
|
||||
v-list-item-title {{tag.title}}
|
||||
v-content.grey(:class='$vuetify.theme.dark ? `darken-4-d5` : `lighten-3`')
|
||||
v-toolbar(color='primary', dark, flat, height='58')
|
||||
template(v-if='selection.length > 0')
|
||||
.overline.mr-3.animated.fadeInLeft {{$t('tags:currentSelection')}}
|
||||
v-chip.mr-3.primary--text(
|
||||
v-for='tag of tagsSelected'
|
||||
:key='`tagSelected-` + tag.tag'
|
||||
color='white'
|
||||
close
|
||||
@click:close='toggleTag(tag.tag)'
|
||||
) {{tag.title}}
|
||||
v-spacer
|
||||
v-btn.animated.fadeIn(
|
||||
small
|
||||
outlined
|
||||
color='blue lighten-4'
|
||||
rounded
|
||||
@click='selection = []'
|
||||
)
|
||||
v-icon(left) mdi-close
|
||||
span {{$t('tags:clearSelection')}}
|
||||
template(v-else)
|
||||
v-icon.mr-3.animated.fadeInRight mdi-arrow-left
|
||||
.overline.animated.fadeInRight {{$t('tags:selectOneMoreTags')}}
|
||||
v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-l5` : `grey lighten-4`', flat, height='58')
|
||||
v-text-field.tags-search(
|
||||
v-model='innerSearch'
|
||||
:label='$t(`tags:searchWithinResultsPlaceholder`)'
|
||||
solo
|
||||
hide-details
|
||||
flat
|
||||
rounded
|
||||
single-line
|
||||
height='40'
|
||||
prepend-icon='mdi-text-box-search-outline'
|
||||
append-icon='mdi-arrow-right'
|
||||
clearable
|
||||
)
|
||||
template(v-if='locales.length > 1')
|
||||
v-divider.mx-3(vertical)
|
||||
.overline {{$t('tags:locale')}}
|
||||
v-select.ml-2(
|
||||
:items='locales'
|
||||
v-model='locale'
|
||||
:background-color='$vuetify.theme.dark ? `grey darken-3` : `white`'
|
||||
hide-details
|
||||
:label='$t(`tags:locale`)'
|
||||
item-text='name'
|
||||
item-value='code'
|
||||
rounded
|
||||
single-line
|
||||
dense
|
||||
height='40'
|
||||
style='max-width: 170px;'
|
||||
)
|
||||
v-divider.mx-3(vertical)
|
||||
.overline {{$t('tags:orderBy')}}
|
||||
v-select.ml-2(
|
||||
:items='orderByItems'
|
||||
v-model='orderBy'
|
||||
:background-color='$vuetify.theme.dark ? `grey darken-3` : `white`'
|
||||
hide-details
|
||||
:label='$t(`tags:orderBy`)'
|
||||
rounded
|
||||
single-line
|
||||
dense
|
||||
height='40'
|
||||
style='max-width: 250px;'
|
||||
)
|
||||
v-btn-toggle.ml-2(v-model='orderByDirection', rounded, mandatory)
|
||||
v-btn(text, height='40'): v-icon(size='20') mdi-chevron-double-up
|
||||
v-btn(text, height='40'): v-icon(size='20') mdi-chevron-double-down
|
||||
v-divider
|
||||
.text-center.pt-10(v-if='selection.length < 1')
|
||||
img(src='/_assets-legacy/svg/icon-price-tag.svg')
|
||||
.subtitle-2.grey--text {{$t('tags:selectOneMoreTagsHint')}}
|
||||
.px-5.py-2(v-else)
|
||||
v-data-iterator(
|
||||
:items='pages'
|
||||
:items-per-page='4'
|
||||
:search='innerSearch'
|
||||
:loading='isLoading'
|
||||
:options.sync='pagination'
|
||||
hide-default-footer
|
||||
ref='dude'
|
||||
)
|
||||
template(v-slot:loading)
|
||||
.text-center.pt-10
|
||||
v-progress-circular(
|
||||
indeterminate
|
||||
color='primary'
|
||||
size='96'
|
||||
width='2'
|
||||
)
|
||||
.subtitle-2.grey--text.mt-5 {{$t('tags:retrievingResultsLoading')}}
|
||||
template(v-slot:no-data)
|
||||
.text-center.pt-10
|
||||
img(src='/_assets-legacy/svg/icon-info.svg')
|
||||
.subtitle-2.grey--text {{$t('tags:noResults')}}
|
||||
template(v-slot:no-results)
|
||||
.text-center.pt-10
|
||||
img(src='/_assets-legacy/svg/icon-info.svg')
|
||||
.subtitle-2.grey--text {{$t('tags:noResultsWithFilter')}}
|
||||
template(v-slot:default='props')
|
||||
v-row(align='stretch')
|
||||
v-col(
|
||||
v-for='item of props.items'
|
||||
:key='`page-` + item.id'
|
||||
cols='12'
|
||||
lg='6'
|
||||
)
|
||||
v-card.radius-7(
|
||||
@click='goTo(item)'
|
||||
style='height:100%;'
|
||||
:class='$vuetify.theme.dark ? `grey darken-4` : ``'
|
||||
)
|
||||
v-card-text
|
||||
.d-flex.flex-row.align-center
|
||||
.body-1: strong.primary--text {{item.title}}
|
||||
v-spacer
|
||||
i18next.caption(tag='div', path='tags:pageLastUpdated')
|
||||
span(place='date') {{item.updatedAt | moment('from')}}
|
||||
.body-2.grey--text {{item.description || '---'}}
|
||||
v-divider.my-2
|
||||
.d-flex.flex-row.align-center
|
||||
v-chip(small, label, :color='$vuetify.theme.dark ? `grey darken-3-l5` : `grey lighten-4`').overline {{item.locale}}
|
||||
.caption.ml-1 / {{item.path}}
|
||||
.text-center.py-2.animated.fadeInDown(v-if='this.pageTotal > 1')
|
||||
v-pagination(v-model='pagination.page', :length='pageTotal')
|
||||
|
||||
nav-footer
|
||||
notify
|
||||
search-results
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueRouter from 'vue-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
import tagsQuery from 'gql/common/common-pages-query-tags.gql'
|
||||
import pagesQuery from 'gql/common/common-pages-query-list.gql'
|
||||
|
||||
/* global siteLangs */
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: '/t'
|
||||
})
|
||||
|
||||
export default {
|
||||
i18nOptions: { namespaces: 'tags' },
|
||||
data() {
|
||||
return {
|
||||
tags: [],
|
||||
selection: [],
|
||||
innerSearch: '',
|
||||
locale: 'any',
|
||||
locales: [],
|
||||
orderBy: 'title',
|
||||
orderByDirection: 0,
|
||||
pagination: {
|
||||
page: 1,
|
||||
itemsPerPage: 12,
|
||||
mustSort: true,
|
||||
sortBy: ['title'],
|
||||
sortDesc: [false]
|
||||
},
|
||||
pages: [],
|
||||
isLoading: true,
|
||||
scrollStyle: {
|
||||
vuescroll: {},
|
||||
scrollPanel: {
|
||||
initialScrollY: 0,
|
||||
initialScrollX: 0,
|
||||
scrollingX: false,
|
||||
easing: 'easeOutQuad',
|
||||
speed: 1000,
|
||||
verticalNativeBarPos: this.$vuetify.rtl ? `left` : `right`
|
||||
},
|
||||
rail: {
|
||||
gutterOfEnds: '2px'
|
||||
},
|
||||
bar: {
|
||||
onlyShowBarOnScroll: false,
|
||||
background: '#CCC',
|
||||
hoverStyle: {
|
||||
background: '#999'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tagsGrouped () {
|
||||
return _.groupBy(this.tags, t => t.title.charAt(0).toUpperCase())
|
||||
},
|
||||
tagsSelected () {
|
||||
return _.filter(this.tags, t => _.includes(this.selection, t.tag))
|
||||
},
|
||||
pageTotal () {
|
||||
return Math.ceil(this.pages.length / this.pagination.itemsPerPage)
|
||||
},
|
||||
orderByItems () {
|
||||
return [
|
||||
{ text: this.$t('tags:orderByField.creationDate'), value: 'createdAt' },
|
||||
{ text: this.$t('tags:orderByField.ID'), value: 'id' },
|
||||
{ text: this.$t('tags:orderByField.lastModified'), value: 'updatedAt' },
|
||||
{ text: this.$t('tags:orderByField.path'), value: 'path' },
|
||||
{ text: this.$t('tags:orderByField.title'), value: 'title' }
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
locale (newValue, oldValue) {
|
||||
this.rebuildURL()
|
||||
},
|
||||
orderBy (newValue, oldValue) {
|
||||
this.rebuildURL()
|
||||
this.pagination.sortBy = [newValue]
|
||||
},
|
||||
orderByDirection (newValue, oldValue) {
|
||||
this.rebuildURL()
|
||||
this.pagination.sortDesc = [newValue === 1]
|
||||
}
|
||||
},
|
||||
router,
|
||||
created () {
|
||||
this.$store.commit('page/SET_MODE', 'tags')
|
||||
this.selection = _.compact(decodeURI(this.$route.path).split('/'))
|
||||
},
|
||||
mounted () {
|
||||
this.locales = _.concat(
|
||||
[{name: this.$t('tags:localeAny'), code: 'any'}],
|
||||
(siteLangs.length > 0 ? siteLangs : [])
|
||||
)
|
||||
if (this.$route.query.lang) {
|
||||
this.locale = this.$route.query.lang
|
||||
}
|
||||
if (this.$route.query.sort) {
|
||||
this.orderBy = this.$route.query.sort.toLowerCase()
|
||||
switch (this.orderBy) {
|
||||
case 'updatedat':
|
||||
this.orderBy = 'updatedAt'
|
||||
break
|
||||
}
|
||||
this.pagination.sortBy = [this.orderBy]
|
||||
}
|
||||
if (this.$route.query.dir) {
|
||||
this.orderByDirection = this.$route.query.dir === 'asc' ? 0 : 1
|
||||
this.pagination.sortDesc = [this.orderByDirection === 1]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleTag (tag) {
|
||||
if (_.includes(this.selection, tag)) {
|
||||
this.selection = _.without(this.selection, tag)
|
||||
} else {
|
||||
this.selection.push(tag)
|
||||
}
|
||||
this.rebuildURL()
|
||||
},
|
||||
isSelected (tag) {
|
||||
return _.includes(this.selection, tag)
|
||||
},
|
||||
rebuildURL () {
|
||||
let urlObj = {
|
||||
path: '/' + this.selection.join('/')
|
||||
}
|
||||
if (this.locale !== `any`) {
|
||||
_.set(urlObj, 'query.lang', this.locale)
|
||||
}
|
||||
if (this.orderBy !== `TITLE`) {
|
||||
_.set(urlObj, 'query.sort', this.orderBy.toLowerCase())
|
||||
}
|
||||
if (this.orderByDirection !== 0) {
|
||||
_.set(urlObj, 'query.dir', this.orderByDirection === 0 ? `asc` : `desc`)
|
||||
}
|
||||
this.$router.push(urlObj)
|
||||
},
|
||||
goTo (page) {
|
||||
window.location.assign(`/${page.locale}/${page.path}`)
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
tags: {
|
||||
query: tagsQuery,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
update: (data) => _.cloneDeep(data.pages.tags),
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'tags-refresh')
|
||||
}
|
||||
},
|
||||
pages: {
|
||||
query: pagesQuery,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
update: (data) => _.cloneDeep(data.pages.list),
|
||||
watchLoading (isLoading) {
|
||||
this.isLoading = isLoading
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'pages-refresh')
|
||||
},
|
||||
variables () {
|
||||
return {
|
||||
locale: this.locale === 'any' ? null : this.locale,
|
||||
tags: this.selection
|
||||
}
|
||||
},
|
||||
skip () {
|
||||
return this.selection.length < 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.tags-search {
|
||||
.v-input__control {
|
||||
min-height: initial !important;
|
||||
}
|
||||
.v-input__prepend-outer {
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,33 +0,0 @@
|
||||
<template lang='pug'>
|
||||
v-app
|
||||
.unauthorized
|
||||
.unauthorized-content
|
||||
img.animated.fadeIn(src='/_assets-legacy/svg/icon-delete-shield.svg', alt='Unauthorized')
|
||||
.headline {{$t('unauthorized.title')}}
|
||||
.subtitle-1.mt-3 {{$t('unauthorized.action.' + action)}}
|
||||
v-btn.mt-5(href='/login', x-large)
|
||||
v-icon(left) mdi-login
|
||||
span {{$t('unauthorized.login')}}
|
||||
v-btn.mt-5(color='red lighten-4', href='javascript:window.history.go(-1);', outlined)
|
||||
v-icon(left) mdi-arrow-left
|
||||
span {{$t('unauthorized.goback')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
default: 'view'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return { }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,8 +0,0 @@
|
||||
query($locale: String!, $namespace: String!) {
|
||||
localization {
|
||||
translations(locale:$locale, namespace:$namespace) {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
mutation($id: Int!) {
|
||||
pages {
|
||||
delete(id: $id) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
mutation($id: Int!, $destinationPath: String!, $destinationLocale: String!) {
|
||||
pages {
|
||||
move(id: $id, destinationPath: $destinationPath, destinationLocale: $destinationLocale) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
query ($limit: Int, $orderBy: PageOrderBy, $orderByDirection: PageOrderByDirection, $tags: [String!], $locale: String) {
|
||||
pages {
|
||||
list(limit: $limit, orderBy: $orderBy, orderByDirection: $orderByDirection, tags: $tags, locale: $locale) {
|
||||
id
|
||||
locale
|
||||
path
|
||||
title
|
||||
description
|
||||
createdAt
|
||||
updatedAt
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
query ($query: String!) {
|
||||
pages {
|
||||
search(query:$query) {
|
||||
results {
|
||||
id
|
||||
title
|
||||
description
|
||||
path
|
||||
locale
|
||||
}
|
||||
suggestions
|
||||
totalHits
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
query {
|
||||
pages {
|
||||
tags {
|
||||
tag
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
query ($parent: Int!, $mode: PageTreeMode!, $locale: String!) {
|
||||
pages {
|
||||
tree(parent: $parent, mode: $mode, locale: $locale) {
|
||||
id
|
||||
path
|
||||
title
|
||||
isFolder
|
||||
pageId
|
||||
parent
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
mutation ($id: Int!) {
|
||||
assets {
|
||||
deleteAsset(id: $id) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
mutation ($id: Int!, $filename: String!) {
|
||||
assets {
|
||||
renameAsset(id:$id, filename: $filename) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
mutation ($parentFolderId: Int!, $slug: String!) {
|
||||
assets {
|
||||
createFolder(parentFolderId:$parentFolderId, slug: $slug) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
query ($parentFolderId: Int!) {
|
||||
assets {
|
||||
folders(parentFolderId:$parentFolderId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
query ($folderId: Int!, $kind: AssetKind!) {
|
||||
assets {
|
||||
list(folderId:$folderId, kind: $kind) {
|
||||
id
|
||||
filename
|
||||
ext
|
||||
kind
|
||||
mime
|
||||
fileSize
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
// =======================================
|
||||
// Fetch polyfill
|
||||
// =======================================
|
||||
// Requirement: Safari 9 and below, IE 11 and below
|
||||
|
||||
if (!window.fetch) {
|
||||
require('whatwg-fetch')
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import filesize from 'filesize.js'
|
||||
import _ from 'lodash'
|
||||
|
||||
/* global siteConfig */
|
||||
|
||||
const helpers = {
|
||||
/**
|
||||
* Convert bytes to humanized form
|
||||
* @param {number} rawSize Size in bytes
|
||||
* @returns {string} Humanized file size
|
||||
*/
|
||||
filesize (rawSize) {
|
||||
return _.toUpper(filesize(rawSize))
|
||||
},
|
||||
/**
|
||||
* Convert raw path to safe path
|
||||
* @param {string} rawPath Raw path
|
||||
* @returns {string} Safe path
|
||||
*/
|
||||
makeSafePath (rawPath) {
|
||||
let rawParts = _.split(_.trim(rawPath), '/')
|
||||
rawParts = _.map(rawParts, (r) => {
|
||||
return _.kebabCase(_.deburr(_.trim(r)))
|
||||
})
|
||||
|
||||
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
|
||||
},
|
||||
resolvePath (path) {
|
||||
if (_.startsWith(path, '/')) { path = path.substring(1) }
|
||||
return `${siteConfig.path}${path}`
|
||||
},
|
||||
/**
|
||||
* Set Input Selection
|
||||
* @param {DOMElement} input The input element
|
||||
* @param {number} startPos The starting position
|
||||
* @param {nunber} endPos The ending position
|
||||
*/
|
||||
setInputSelection (input, startPos, endPos) {
|
||||
input.focus()
|
||||
if (typeof input.selectionStart !== 'undefined') {
|
||||
input.selectionStart = startPos
|
||||
input.selectionEnd = endPos
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
// IE branch
|
||||
input.select()
|
||||
var range = document.selection.createRange()
|
||||
range.collapse(true)
|
||||
range.moveEnd('character', endPos)
|
||||
range.moveStart('character', startPos)
|
||||
range.select()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
Vue.$helpers = helpers
|
||||
Object.defineProperties(Vue.prototype, {
|
||||
$helpers: {
|
||||
get() {
|
||||
return helpers
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
require('core-js/stable')
|
||||
require('regenerator-runtime/runtime')
|
||||
|
||||
/* eslint-disable no-unused-expressions */
|
||||
|
||||
switch (window.document.documentElement.lang) {
|
||||
case 'ar':
|
||||
case 'fa':
|
||||
import(/* webpackChunkName: "fonts-arabic" */ './scss/fonts/arabic.scss')
|
||||
break
|
||||
default:
|
||||
import(/* webpackChunkName: "fonts-default" */ './scss/fonts/default.scss')
|
||||
break
|
||||
}
|
||||
|
||||
require('modernizr')
|
||||
|
||||
require('./scss/app.scss')
|
||||
import(/* webpackChunkName: "theme" */ './themes/default/scss/app.scss')
|
||||
|
||||
import(/* webpackChunkName: "mdi" */ '@mdi/font/css/materialdesignicons.css')
|
||||
|
||||
require('./helpers/compatibility.js')
|
||||
require('./client-app.js')
|
||||
import(/* webpackChunkName: "theme" */ './themes/default/js/app.js')
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
var diff_match_patch=function(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=.5;this.Patch_Margin=4;this.Match_MaxBits=32},DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0;diff_match_patch.Diff=function(a,b){this[0]=a;this[1]=b};diff_match_patch.Diff.prototype.length=2;diff_match_patch.Diff.prototype.toString=function(){return this[0]+","+this[1]};
|
||||
diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[new diff_match_patch.Diff(DIFF_EQUAL,a)]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);f=this.diff_commonSuffix(a,b);var g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,
|
||||
b.length-f);a=this.diff_compute_(a,b,e,d);c&&a.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));g&&a.push(new diff_match_patch.Diff(DIFF_EQUAL,g));this.diff_cleanupMerge(a);return a};
|
||||
diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[new diff_match_patch.Diff(DIFF_INSERT,b)];if(!b)return[new diff_match_patch.Diff(DIFF_DELETE,a)];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[new diff_match_patch.Diff(DIFF_INSERT,e.substring(0,g)),new diff_match_patch.Diff(DIFF_EQUAL,f),new diff_match_patch.Diff(DIFF_INSERT,e.substring(g+f.length))],a.length>b.length&&(c[0][0]=c[2][0]=DIFF_DELETE),c):1==f.length?[new diff_match_patch.Diff(DIFF_DELETE,
|
||||
a),new diff_match_patch.Diff(DIFF_INSERT,b)]:(e=this.diff_halfMatch_(a,b))?(b=e[1],f=e[3],a=e[4],e=this.diff_main(e[0],e[2],c,d),c=this.diff_main(b,f,c,d),e.concat([new diff_match_patch.Diff(DIFF_EQUAL,a)],c)):c&&100<a.length&&100<b.length?this.diff_lineMode_(a,b,d):this.diff_bisect_(a,b,d)};
|
||||
diff_match_patch.prototype.diff_lineMode_=function(a,b,c){var d=this.diff_linesToChars_(a,b);a=d.chars1;b=d.chars2;d=d.lineArray;a=this.diff_main(a,b,!1,c);this.diff_charsToLines_(a,d);this.diff_cleanupSemantic(a);a.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var e=d=b=0,f="",g="";b<a.length;){switch(a[b][0]){case DIFF_INSERT:e++;g+=a[b][1];break;case DIFF_DELETE:d++;f+=a[b][1];break;case DIFF_EQUAL:if(1<=d&&1<=e){a.splice(b-d-e,d+e);b=b-d-e;d=this.diff_main(f,g,!1,c);for(e=d.length-1;0<=e;e--)a.splice(b,
|
||||
0,d[e]);b+=d.length}d=e=0;g=f=""}b++}a.pop();return a};
|
||||
diff_match_patch.prototype.diff_bisect_=function(a,b,c){for(var d=a.length,e=b.length,f=Math.ceil((d+e)/2),g=2*f,h=Array(g),l=Array(g),k=0;k<g;k++)h[k]=-1,l[k]=-1;h[f+1]=0;l[f+1]=0;k=d-e;for(var m=0!=k%2,p=0,x=0,w=0,q=0,t=0;t<f&&!((new Date).getTime()>c);t++){for(var v=-t+p;v<=t-x;v+=2){var n=f+v;var r=v==-t||v!=t&&h[n-1]<h[n+1]?h[n+1]:h[n-1]+1;for(var y=r-v;r<d&&y<e&&a.charAt(r)==b.charAt(y);)r++,y++;h[n]=r;if(r>d)x+=2;else if(y>e)p+=2;else if(m&&(n=f+k-v,0<=n&&n<g&&-1!=l[n])){var u=d-l[n];if(r>=
|
||||
u)return this.diff_bisectSplit_(a,b,r,y,c)}}for(v=-t+w;v<=t-q;v+=2){n=f+v;u=v==-t||v!=t&&l[n-1]<l[n+1]?l[n+1]:l[n-1]+1;for(r=u-v;u<d&&r<e&&a.charAt(d-u-1)==b.charAt(e-r-1);)u++,r++;l[n]=u;if(u>d)q+=2;else if(r>e)w+=2;else if(!m&&(n=f+k-v,0<=n&&n<g&&-1!=h[n]&&(r=h[n],y=f+r-n,u=d-u,r>=u)))return this.diff_bisectSplit_(a,b,r,y,c)}}return[new diff_match_patch.Diff(DIFF_DELETE,a),new diff_match_patch.Diff(DIFF_INSERT,b)]};
|
||||
diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)};
|
||||
diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,g=-1,h=d.length;g<a.length-1;){g=a.indexOf("\n",c);-1==g&&(g=a.length-1);var l=a.substring(c,g+1);(e.hasOwnProperty?e.hasOwnProperty(l):void 0!==e[l])?b+=String.fromCharCode(e[l]):(h==f&&(l=a.substring(c),g=a.length),b+=String.fromCharCode(h),e[l]=h,d[h++]=l);c=g+1}return b}var d=[],e={};d[0]="";var f=4E4,g=c(a);f=65535;var h=c(b);return{chars1:g,chars2:h,lineArray:d}};
|
||||
diff_match_patch.prototype.diff_charsToLines_=function(a,b){for(var c=0;c<a.length;c++){for(var d=a[c][1],e=[],f=0;f<d.length;f++)e[f]=b[d.charCodeAt(f)];a[c][1]=e.join("")}};diff_match_patch.prototype.diff_commonPrefix=function(a,b){if(!a||!b||a.charAt(0)!=b.charAt(0))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(f,e)==b.substring(f,e)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
|
||||
diff_match_patch.prototype.diff_commonSuffix=function(a,b){if(!a||!b||a.charAt(a.length-1)!=b.charAt(b.length-1))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(a.length-e,a.length-f)==b.substring(b.length-e,b.length-f)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
|
||||
diff_match_patch.prototype.diff_commonOverlap_=function(a,b){var c=a.length,d=b.length;if(0==c||0==d)return 0;c>d?a=a.substring(c-d):c<d&&(b=b.substring(0,c));c=Math.min(c,d);if(a==b)return c;d=0;for(var e=1;;){var f=a.substring(c-e);f=b.indexOf(f);if(-1==f)return d;e+=f;if(0==f||a.substring(c-e)==b.substring(0,e))d=e,e++}};
|
||||
diff_match_patch.prototype.diff_halfMatch_=function(a,b){function c(a,b,c){for(var d=a.substring(c,c+Math.floor(a.length/4)),e=-1,g="",h,k,l,m;-1!=(e=b.indexOf(d,e+1));){var p=f.diff_commonPrefix(a.substring(c),b.substring(e)),u=f.diff_commonSuffix(a.substring(0,c),b.substring(0,e));g.length<u+p&&(g=b.substring(e-u,e)+b.substring(e,e+p),h=a.substring(0,c-u),k=a.substring(c+p),l=b.substring(0,e-u),m=b.substring(e+p))}return 2*g.length>=a.length?[h,k,l,m,g]:null}if(0>=this.Diff_Timeout)return null;
|
||||
var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.length<d.length)return null;var f=this,g=c(d,e,Math.ceil(d.length/4));d=c(d,e,Math.ceil(d.length/2));if(g||d)g=d?g?g[4].length>d[4].length?g:d:d:g;else return null;if(a.length>b.length){d=g[0];e=g[1];var h=g[2];var l=g[3]}else h=g[0],l=g[1],d=g[2],e=g[3];return[d,e,h,l,g[4]]};
|
||||
diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,l=0,k=0;f<a.length;)a[f][0]==DIFF_EQUAL?(c[d++]=f,g=l,h=k,k=l=0,e=a[f][1]):(a[f][0]==DIFF_INSERT?l+=a[f][1].length:k+=a[f][1].length,e&&e.length<=Math.max(g,h)&&e.length<=Math.max(l,k)&&(a.splice(c[d-1],0,new diff_match_patch.Diff(DIFF_DELETE,e)),a[c[d-1]+1][0]=DIFF_INSERT,d--,d--,f=0<d?c[d-1]:-1,k=l=h=g=0,e=null,b=!0)),f++;b&&this.diff_cleanupMerge(a);this.diff_cleanupSemanticLossless(a);for(f=1;f<
|
||||
a.length;){if(a[f-1][0]==DIFF_DELETE&&a[f][0]==DIFF_INSERT){b=a[f-1][1];c=a[f][1];d=this.diff_commonOverlap_(b,c);e=this.diff_commonOverlap_(c,b);if(d>=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,new diff_match_patch.Diff(DIFF_EQUAL,c.substring(0,d))),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,new diff_match_patch.Diff(DIFF_EQUAL,b.substring(0,e))),a[f-1][0]=DIFF_INSERT,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=DIFF_DELETE,
|
||||
a[f+1][1]=b.substring(e),f++;f++}f++}};
|
||||
diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_);c=g&&c.match(diff_match_patch.linebreakRegex_);d=h&&d.match(diff_match_patch.linebreakRegex_);var k=c&&a.match(diff_match_patch.blanklineEndRegex_),l=d&&b.match(diff_match_patch.blanklineStartRegex_);
|
||||
return k||l?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c<a.length-1;){if(a[c-1][0]==DIFF_EQUAL&&a[c+1][0]==DIFF_EQUAL){var d=a[c-1][1],e=a[c][1],f=a[c+1][1],g=this.diff_commonSuffix(d,e);if(g){var h=e.substring(e.length-g);d=d.substring(0,d.length-g);e=h+e.substring(0,e.length-g);f=h+f}g=d;h=e;for(var l=f,k=b(d,e)+b(e,f);e.charAt(0)===f.charAt(0);){d+=e.charAt(0);e=e.substring(1)+f.charAt(0);f=f.substring(1);var m=b(d,e)+b(e,f);m>=k&&(k=m,g=d,h=e,l=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-
|
||||
1,1),c--),a[c][1]=h,l?a[c+1][1]=l:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/;
|
||||
diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,l=!1,k=!1;f<a.length;)a[f][0]==DIFF_EQUAL?(a[f][1].length<this.Diff_EditCost&&(l||k)?(c[d++]=f,g=l,h=k,e=a[f][1]):(d=0,e=null),l=k=!1):(a[f][0]==DIFF_DELETE?k=!0:l=!0,e&&(g&&h&&l&&k||e.length<this.Diff_EditCost/2&&3==g+h+l+k)&&(a.splice(c[d-1],0,new diff_match_patch.Diff(DIFF_DELETE,e)),a[c[d-1]+1][0]=DIFF_INSERT,d--,e=null,g&&h?(l=k=!0,d=0):(d--,f=0<d?c[d-1]:-1,l=k=!1),b=!0)),f++;b&&this.diff_cleanupMerge(a)};
|
||||
diff_match_patch.prototype.diff_cleanupMerge=function(a){a.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var b=0,c=0,d=0,e="",f="",g;b<a.length;)switch(a[b][0]){case DIFF_INSERT:d++;f+=a[b][1];b++;break;case DIFF_DELETE:c++;e+=a[b][1];b++;break;case DIFF_EQUAL:1<c+d?(0!==c&&0!==d&&(g=this.diff_commonPrefix(f,e),0!==g&&(0<b-c-d&&a[b-c-d-1][0]==DIFF_EQUAL?a[b-c-d-1][1]+=f.substring(0,g):(a.splice(0,0,new diff_match_patch.Diff(DIFF_EQUAL,f.substring(0,g))),b++),f=f.substring(g),e=e.substring(g)),
|
||||
g=this.diff_commonSuffix(f,e),0!==g&&(a[b][1]=f.substring(f.length-g)+a[b][1],f=f.substring(0,f.length-g),e=e.substring(0,e.length-g))),b-=c+d,a.splice(b,c+d),e.length&&(a.splice(b,0,new diff_match_patch.Diff(DIFF_DELETE,e)),b++),f.length&&(a.splice(b,0,new diff_match_patch.Diff(DIFF_INSERT,f)),b++),b++):0!==b&&a[b-1][0]==DIFF_EQUAL?(a[b-1][1]+=a[b][1],a.splice(b,1)):b++,c=d=0,f=e=""}""===a[a.length-1][1]&&a.pop();c=!1;for(b=1;b<a.length-1;)a[b-1][0]==DIFF_EQUAL&&a[b+1][0]==DIFF_EQUAL&&(a[b][1].substring(a[b][1].length-
|
||||
a[b-1][1].length)==a[b-1][1]?(a[b][1]=a[b-1][1]+a[b][1].substring(0,a[b][1].length-a[b-1][1].length),a[b+1][1]=a[b-1][1]+a[b+1][1],a.splice(b-1,1),c=!0):a[b][1].substring(0,a[b+1][1].length)==a[b+1][1]&&(a[b-1][1]+=a[b+1][1],a[b][1]=a[b][1].substring(a[b+1][1].length)+a[b+1][1],a.splice(b+1,1),c=!0)),b++;c&&this.diff_cleanupMerge(a)};
|
||||
diff_match_patch.prototype.diff_xIndex=function(a,b){var c=0,d=0,e=0,f=0,g;for(g=0;g<a.length;g++){a[g][0]!==DIFF_INSERT&&(c+=a[g][1].length);a[g][0]!==DIFF_DELETE&&(d+=a[g][1].length);if(c>b)break;e=c;f=d}return a.length!=g&&a[g][0]===DIFF_DELETE?f:f+(b-e)};
|
||||
diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=/</g,e=/>/g,f=/\n/g,g=0;g<a.length;g++){var h=a[g][0],l=a[g][1].replace(c,"&").replace(d,"<").replace(e,">").replace(f,"¶<br>");switch(h){case DIFF_INSERT:b[g]='<ins style="background:#e6ffe6;">'+l+"</ins>";break;case DIFF_DELETE:b[g]='<del style="background:#ffe6e6;">'+l+"</del>";break;case DIFF_EQUAL:b[g]="<span>"+l+"</span>"}}return b.join("")};
|
||||
diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;c<a.length;c++)a[c][0]!==DIFF_INSERT&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_text2=function(a){for(var b=[],c=0;c<a.length;c++)a[c][0]!==DIFF_DELETE&&(b[c]=a[c][1]);return b.join("")};
|
||||
diff_match_patch.prototype.diff_levenshtein=function(a){for(var b=0,c=0,d=0,e=0;e<a.length;e++){var f=a[e][1];switch(a[e][0]){case DIFF_INSERT:c+=f.length;break;case DIFF_DELETE:d+=f.length;break;case DIFF_EQUAL:b+=Math.max(c,d),d=c=0}}return b+=Math.max(c,d)};
|
||||
diff_match_patch.prototype.diff_toDelta=function(a){for(var b=[],c=0;c<a.length;c++)switch(a[c][0]){case DIFF_INSERT:b[c]="+"+encodeURI(a[c][1]);break;case DIFF_DELETE:b[c]="-"+a[c][1].length;break;case DIFF_EQUAL:b[c]="="+a[c][1].length}return b.join("\t").replace(/%20/g," ")};
|
||||
diff_match_patch.prototype.diff_fromDelta=function(a,b){for(var c=[],d=0,e=0,f=b.split(/\t/g),g=0;g<f.length;g++){var h=f[g].substring(1);switch(f[g].charAt(0)){case "+":try{c[d++]=new diff_match_patch.Diff(DIFF_INSERT,decodeURI(h))}catch(k){throw Error("Illegal escape in diff_fromDelta: "+h);}break;case "-":case "=":var l=parseInt(h,10);if(isNaN(l)||0>l)throw Error("Invalid number in diff_fromDelta: "+h);h=a.substring(e,e+=l);"="==f[g].charAt(0)?c[d++]=new diff_match_patch.Diff(DIFF_EQUAL,h):c[d++]=
|
||||
new diff_match_patch.Diff(DIFF_DELETE,h);break;default:if(f[g])throw Error("Invalid diff operation in diff_fromDelta: "+f[g]);}}if(e!=a.length)throw Error("Delta length ("+e+") does not equal source text length ("+a.length+").");return c};diff_match_patch.prototype.match_main=function(a,b,c){if(null==a||null==b||null==c)throw Error("Null input. (match_main)");c=Math.max(0,Math.min(c,a.length));return a==b?0:a.length?a.substring(c,c+b.length)==b?c:this.match_bitap_(a,b,c):-1};
|
||||
diff_match_patch.prototype.match_bitap_=function(a,b,c){function d(a,d){var e=a/b.length,g=Math.abs(c-d);return f.Match_Distance?e+g/f.Match_Distance:g?1:e}if(b.length>this.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));var l=1<<b.length-1;h=-1;for(var k,m,p=b.length+a.length,x,w=0;w<b.length;w++){k=0;for(m=p;k<m;)d(w,
|
||||
c+m)<=g?k=m:p=m,m=Math.floor((p-k)/2+k);p=m;k=Math.max(1,c-m+1);var q=Math.min(c+m,a.length)+b.length;m=Array(q+2);for(m[q+1]=(1<<w)-1;q>=k;q--){var t=e[a.charAt(q-1)];m[q]=0===w?(m[q+1]<<1|1)&t:(m[q+1]<<1|1)&t|(x[q+1]|x[q])<<1|1|x[q+1];if(m[q]&l&&(t=d(w,q-1),t<=g))if(g=t,h=q-1,h>c)k=Math.max(1,2*c-h);else break}if(d(w+1,c)>g)break;x=m}return h};
|
||||
diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c<a.length;c++)b[a.charAt(c)]=0;for(c=0;c<a.length;c++)b[a.charAt(c)]|=1<<a.length-c-1;return b};
|
||||
diff_match_patch.prototype.patch_addContext_=function(a,b){if(0!=b.length){if(null===a.start2)throw Error("patch not initialized");for(var c=b.substring(a.start2,a.start2+a.length1),d=0;b.indexOf(c)!=b.lastIndexOf(c)&&c.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)d+=this.Patch_Margin,c=b.substring(a.start2-d,a.start2+a.length1+d);d+=this.Patch_Margin;(c=b.substring(a.start2-d,a.start2))&&a.diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));(d=b.substring(a.start2+a.length1,
|
||||
a.start2+a.length1+d))&&a.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,d));a.start1-=c.length;a.start2-=c.length;a.length1+=c.length+d.length;a.length2+=c.length+d.length}};
|
||||
diff_match_patch.prototype.patch_make=function(a,b,c){if("string"==typeof a&&"string"==typeof b&&"undefined"==typeof c){var d=a;b=this.diff_main(d,b,!0);2<b.length&&(this.diff_cleanupSemantic(b),this.diff_cleanupEfficiency(b))}else if(a&&"object"==typeof a&&"undefined"==typeof b&&"undefined"==typeof c)b=a,d=this.diff_text1(b);else if("string"==typeof a&&b&&"object"==typeof b&&"undefined"==typeof c)d=a;else if("string"==typeof a&&"string"==typeof b&&c&&"object"==typeof c)d=a,b=c;else throw Error("Unknown call format to patch_make.");
|
||||
if(0===b.length)return[];c=[];a=new diff_match_patch.patch_obj;for(var e=0,f=0,g=0,h=d,l=0;l<b.length;l++){var k=b[l][0],m=b[l][1];e||k===DIFF_EQUAL||(a.start1=f,a.start2=g);switch(k){case DIFF_INSERT:a.diffs[e++]=b[l];a.length2+=m.length;d=d.substring(0,g)+m+d.substring(g);break;case DIFF_DELETE:a.length1+=m.length;a.diffs[e++]=b[l];d=d.substring(0,g)+d.substring(g+m.length);break;case DIFF_EQUAL:m.length<=2*this.Patch_Margin&&e&&b.length!=l+1?(a.diffs[e++]=b[l],a.length1+=m.length,a.length2+=m.length):
|
||||
m.length>=2*this.Patch_Margin&&e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}k!==DIFF_INSERT&&(f+=m.length);k!==DIFF_DELETE&&(g+=m.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};
|
||||
diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c],e=new diff_match_patch.patch_obj;e.diffs=[];for(var f=0;f<d.diffs.length;f++)e.diffs[f]=new diff_match_patch.Diff(d.diffs[f][0],d.diffs[f][1]);e.start1=d.start1;e.start2=d.start2;e.length1=d.length1;e.length2=d.length2;b[c]=e}return b};
|
||||
diff_match_patch.prototype.patch_apply=function(a,b){if(0==a.length)return[b,[]];a=this.patch_deepCopy(a);var c=this.patch_addPadding(a);b=c+b+c;this.patch_splitMax(a);for(var d=0,e=[],f=0;f<a.length;f++){var g=a[f].start2+d,h=this.diff_text1(a[f].diffs),l=-1;if(h.length>this.Match_MaxBits){var k=this.match_main(b,h.substring(0,this.Match_MaxBits),g);-1!=k&&(l=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==l||k>=l)&&(k=-1)}else k=this.match_main(b,h,
|
||||
g);if(-1==k)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=k-g,g=-1==l?b.substring(k,k+h.length):b.substring(k,l+this.Match_MaxBits),h==g)b=b.substring(0,k)+this.diff_text2(a[f].diffs)+b.substring(k+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);h=0;var m;for(l=0;l<a[f].diffs.length;l++){var p=a[f].diffs[l];p[0]!==DIFF_EQUAL&&(m=this.diff_xIndex(g,h));p[0]===
|
||||
DIFF_INSERT?b=b.substring(0,k+m)+p[1]+b.substring(k+m):p[0]===DIFF_DELETE&&(b=b.substring(0,k+m)+b.substring(k+this.diff_xIndex(g,h+p[1].length)));p[0]!==DIFF_DELETE&&(h+=p[1].length)}}}b=b.substring(c.length,b.length-c.length);return[b,e]};
|
||||
diff_match_patch.prototype.patch_addPadding=function(a){for(var b=this.Patch_Margin,c="",d=1;d<=b;d++)c+=String.fromCharCode(d);for(d=0;d<a.length;d++)a[d].start1+=b,a[d].start2+=b;d=a[0];var e=d.diffs;if(0==e.length||e[0][0]!=DIFF_EQUAL)e.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c)),d.start1-=b,d.start2-=b,d.length1+=b,d.length2+=b;else if(b>e[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;
|
||||
0==e.length||e[e.length-1][0]!=DIFF_EQUAL?(e.push(new diff_match_patch.Diff(DIFF_EQUAL,c)),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c};
|
||||
diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c<a.length;c++)if(!(a[c].length1<=b)){var d=a[c];a.splice(c--,1);for(var e=d.start1,f=d.start2,g="";0!==d.diffs.length;){var h=new diff_match_patch.patch_obj,l=!0;h.start1=e-g.length;h.start2=f-g.length;""!==g&&(h.length1=h.length2=g.length,h.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,g)));for(;0!==d.diffs.length&&h.length1<b-this.Patch_Margin;){g=d.diffs[0][0];var k=d.diffs[0][1];g===DIFF_INSERT?(h.length2+=
|
||||
k.length,f+=k.length,h.diffs.push(d.diffs.shift()),l=!1):g===DIFF_DELETE&&1==h.diffs.length&&h.diffs[0][0]==DIFF_EQUAL&&k.length>2*b?(h.length1+=k.length,e+=k.length,l=!1,h.diffs.push(new diff_match_patch.Diff(g,k)),d.diffs.shift()):(k=k.substring(0,b-h.length1-this.Patch_Margin),h.length1+=k.length,e+=k.length,g===DIFF_EQUAL?(h.length2+=k.length,f+=k.length):l=!1,h.diffs.push(new diff_match_patch.Diff(g,k)),k==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(k.length))}g=this.diff_text2(h.diffs);
|
||||
g=g.substring(g.length-this.Patch_Margin);k=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==k&&(h.length1+=k.length,h.length2+=k.length,0!==h.diffs.length&&h.diffs[h.diffs.length-1][0]===DIFF_EQUAL?h.diffs[h.diffs.length-1][1]+=k:h.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,k)));l||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c<a.length;c++)b[c]=a[c];return b.join("")};
|
||||
diff_match_patch.prototype.patch_fromText=function(a){var b=[];if(!a)return b;a=a.split("\n");for(var c=0,d=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;c<a.length;){var e=a[c].match(d);if(!e)throw Error("Invalid patch string: "+a[c]);var f=new diff_match_patch.patch_obj;b.push(f);f.start1=parseInt(e[1],10);""===e[2]?(f.start1--,f.length1=1):"0"==e[2]?f.length1=0:(f.start1--,f.length1=parseInt(e[2],10));f.start2=parseInt(e[3],10);""===e[4]?(f.start2--,f.length2=1):"0"==e[4]?f.length2=0:(f.start2--,f.length2=
|
||||
parseInt(e[4],10));for(c++;c<a.length;){e=a[c].charAt(0);try{var g=decodeURI(a[c].substring(1))}catch(h){throw Error("Illegal escape in patch_fromText: "+g);}if("-"==e)f.diffs.push(new diff_match_patch.Diff(DIFF_DELETE,g));else if("+"==e)f.diffs.push(new diff_match_patch.Diff(DIFF_INSERT,g));else if(" "==e)f.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,g));else if("@"==e)break;else if(""!==e)throw Error('Invalid patch mode "'+e+'" in: '+g);c++}}return b};
|
||||
diff_match_patch.patch_obj=function(){this.diffs=[];this.start2=this.start1=null;this.length2=this.length1=0};
|
||||
diff_match_patch.patch_obj.prototype.toString=function(){for(var a=["@@ -"+(0===this.length1?this.start1+",0":1==this.length1?this.start1+1:this.start1+1+","+this.length1)+" +"+(0===this.length2?this.start2+",0":1==this.length2?this.start2+1:this.start2+1+","+this.length2)+" @@\n"],b,c=0;c<this.diffs.length;c++){switch(this.diffs[c][0]){case DIFF_INSERT:b="+";break;case DIFF_DELETE:b="-";break;case DIFF_EQUAL:b=" "}a[c+1]=b+encodeURI(this.diffs[c][1])+"\n"}return a.join("").replace(/%20/g," ")};
|
||||
this.diff_match_patch=diff_match_patch;this.DIFF_DELETE=DIFF_DELETE;this.DIFF_INSERT=DIFF_INSERT;this.DIFF_EQUAL=DIFF_EQUAL;
|
||||
|
||||
window.diff_match_patch = diff_match_patch
|
||||
window.DIFF_INSERT = DIFF_INSERT
|
||||
window.DIFF_DELETE = DIFF_DELETE
|
||||
window.DIFF_EQUAL = DIFF_EQUAL
|
@ -1,12 +0,0 @@
|
||||
const renderEm = (tokens, idx, opts, env, slf) => {
|
||||
const token = tokens[idx];
|
||||
if (token.markup === '_') {
|
||||
token.tag = 'u';
|
||||
}
|
||||
return slf.renderToken(tokens, idx, opts);
|
||||
}
|
||||
|
||||
module.exports = (md) => {
|
||||
md.renderer.rules.em_open = renderEm;
|
||||
md.renderer.rules.em_close = renderEm;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
/*! modernizr 3.6.0 (Custom Build) | MIT *
|
||||
* https://modernizr.com/download/?-setclasses !*/
|
||||
!function(n,e,s){function o(n){var e=r.className,s=Modernizr._config.classPrefix||"";if(c&&(e=e.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+s+"no-js(\\s|$)");e=e.replace(o,"$1"+s+"js$2")}Modernizr._config.enableClasses&&(e+=" "+s+n.join(" "+s),c?r.className.baseVal=e:r.className=e)}function a(n,e){return typeof n===e}function i(){var n,e,s,o,i,l,r;for(var c in f)if(f.hasOwnProperty(c)){if(n=[],e=f[c],e.name&&(n.push(e.name.toLowerCase()),e.options&&e.options.aliases&&e.options.aliases.length))for(s=0;s<e.options.aliases.length;s++)n.push(e.options.aliases[s].toLowerCase());for(o=a(e.fn,"function")?e.fn():e.fn,i=0;i<n.length;i++)l=n[i],r=l.split("."),1===r.length?Modernizr[r[0]]=o:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=o),t.push((o?"":"no-")+r.join("-"))}}var t=[],f=[],l={_version:"3.6.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(n,e){var s=this;setTimeout(function(){e(s[n])},0)},addTest:function(n,e,s){f.push({name:n,fn:e,options:s})},addAsyncTest:function(n){f.push({name:null,fn:n})}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var r=e.documentElement,c="svg"===r.nodeName.toLowerCase();i(),o(t),delete l.addTest,delete l.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();n.Modernizr=Modernizr}(window,document);
|
@ -1,172 +0,0 @@
|
||||
/* PrismJS 1.11.0
|
||||
http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+c+bash+basic+cpp+csharp+arduino+ruby+elixir+fsharp+go+graphql+handlebars+haskell+ini+java+json+kotlin+latex+less+makefile+markdown+matlab+nginx+objectivec+perl+php+powershell+pug+python+typescript+rust+scss+scala+smalltalk+sql+stylus+swift+vbnet+yaml&plugins=line-numbers */
|
||||
/**
|
||||
* prism.js Dark theme for JavaScript, CSS and HTML
|
||||
* Based on the slides of the talk “/Reg(exp){2}lained/”
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: white;
|
||||
background: none;
|
||||
text-shadow: 0 -.1em .2em black;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
pre[class*="language-"],
|
||||
:not(pre) > code[class*="language-"] {
|
||||
background: hsl(30, 20%, 25%);
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border: .3em solid hsl(30, 20%, 40%);
|
||||
border-radius: .5em;
|
||||
box-shadow: 1px 1px .5em black inset;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .15em .2em .05em;
|
||||
border-radius: .3em;
|
||||
border: .13em solid hsl(30, 20%, 40%);
|
||||
box-shadow: 1px 1px .3em -.1em black inset;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: hsl(30, 20%, 50%);
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol {
|
||||
color: hsl(350, 40%, 70%);
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: hsl(75, 70%, 60%);
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: hsl(40, 90%, 60%);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: hsl(350, 40%, 70%);
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: red;
|
||||
}
|
||||
|
||||
pre.line-numbers {
|
||||
position: relative;
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
}
|
||||
|
||||
pre.line-numbers > code {
|
||||
position: relative;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.line-numbers .line-numbers-rows {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
font-size: 100%;
|
||||
left: -3.8em;
|
||||
width: 3em; /* works for line-numbers below 1000 lines */
|
||||
letter-spacing: -1px;
|
||||
border-right: 1px solid #999;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
.line-numbers-rows > span {
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
counter-increment: linenumber;
|
||||
}
|
||||
|
||||
.line-numbers-rows > span:before {
|
||||
content: counter(linenumber);
|
||||
color: #999;
|
||||
display: block;
|
||||
padding-right: 0.8em;
|
||||
text-align: right;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,68 +0,0 @@
|
||||
export default {
|
||||
readyStates: [],
|
||||
callbacks: [],
|
||||
/**
|
||||
* Check if event has been sent
|
||||
*
|
||||
* @param {String} evt Event name
|
||||
* @returns {Boolean} True if fired
|
||||
*/
|
||||
isReady (evt) {
|
||||
return this.readyStates.indexOf(evt) >= 0
|
||||
},
|
||||
/**
|
||||
* Register a callback to be executed when event is sent
|
||||
*
|
||||
* @param {String} evt Event name to register to
|
||||
* @param {Function} clb Callback function
|
||||
* @param {Boolean} once If the callback should be called only once
|
||||
*/
|
||||
register (evt, clb, once) {
|
||||
if (this.isReady(evt)) {
|
||||
clb()
|
||||
} else {
|
||||
this.callbacks.push({
|
||||
event: evt,
|
||||
callback: clb,
|
||||
once: false,
|
||||
called: false
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Register a callback to be executed only once when event is sent
|
||||
*
|
||||
* @param {String} evt Event name to register to
|
||||
* @param {Function} clb Callback function
|
||||
*/
|
||||
registerOnce (evt, clb) {
|
||||
this.register(evt, clb, true)
|
||||
},
|
||||
/**
|
||||
* Set ready state and execute callbacks
|
||||
*/
|
||||
notify (evt) {
|
||||
this.readyStates.push(evt)
|
||||
this.callbacks.forEach(clb => {
|
||||
if (clb.event === evt) {
|
||||
if (clb.once && clb.called) {
|
||||
return
|
||||
}
|
||||
clb.called = true
|
||||
clb.callback()
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Execute callback on DOM ready
|
||||
*
|
||||
* @param {Function} clb Callback function
|
||||
*/
|
||||
onDOMReady (clb) {
|
||||
if (document.readyState === 'interactive' || document.readyState === 'complete' || document.readyState === 'loaded') {
|
||||
clb()
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', clb)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import i18next from 'i18next'
|
||||
import Backend from 'i18next-chained-backend'
|
||||
import LocalStorageBackend from 'i18next-localstorage-backend'
|
||||
import i18nextXHR from 'i18next-xhr-backend'
|
||||
import VueI18Next from '@panter/vue-i18next'
|
||||
import _ from 'lodash'
|
||||
|
||||
/* global siteConfig, graphQL */
|
||||
|
||||
import localeQuery from 'gql/common/common-localization-query-translations.gql'
|
||||
|
||||
export default {
|
||||
VueI18Next,
|
||||
init() {
|
||||
i18next
|
||||
.use(Backend)
|
||||
.init({
|
||||
backend: {
|
||||
backends: [
|
||||
LocalStorageBackend,
|
||||
i18nextXHR
|
||||
],
|
||||
backendOptions: [
|
||||
{
|
||||
expirationTime: 1000 * 60 * 60 * 24 // 24h
|
||||
},
|
||||
{
|
||||
loadPath: '{{lng}}/{{ns}}',
|
||||
parse: (data) => data,
|
||||
ajax: (url, opts, cb, data) => {
|
||||
let ns = {}
|
||||
return cb(ns, {status: '200'})
|
||||
// let langParams = url.split('/')
|
||||
// graphQL.query({
|
||||
// query: localeQuery,
|
||||
// variables: {
|
||||
// locale: langParams[0],
|
||||
// namespace: langParams[1]
|
||||
// }
|
||||
// }).then(resp => {
|
||||
// let ns = {}
|
||||
// if (_.get(resp, 'data.localization.translations', []).length > 0) {
|
||||
// resp.data.localization.translations.forEach(entry => {
|
||||
// _.set(ns, entry.key, entry.value)
|
||||
// })
|
||||
// }
|
||||
// return cb(ns, {status: '200'})
|
||||
// }).catch(err => {
|
||||
// console.error(err)
|
||||
// return cb(null, {status: '404'})
|
||||
// })
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
defaultNS: 'common',
|
||||
lng: siteConfig.lang,
|
||||
load: 'currentOnly',
|
||||
lowerCaseLng: true,
|
||||
fallbackLng: siteConfig.lang,
|
||||
ns: ['common', 'auth']
|
||||
})
|
||||
return new VueI18Next(i18next)
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
// Production steps of ECMA-262, Edition 6, 22.1.2.1
|
||||
if (!Array.from) {
|
||||
Array.from = (function () {
|
||||
var toStr = Object.prototype.toString
|
||||
var isCallable = function (fn) {
|
||||
return typeof fn === 'function' || toStr.call(fn) === '[object Function]'
|
||||
}
|
||||
var toInteger = function (value) {
|
||||
var number = Number(value)
|
||||
if (isNaN(number)) { return 0 }
|
||||
if (number === 0 || !isFinite(number)) { return number }
|
||||
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number))
|
||||
}
|
||||
var maxSafeInteger = Math.pow(2, 53) - 1
|
||||
var toLength = function (value) {
|
||||
var len = toInteger(value)
|
||||
return Math.min(Math.max(len, 0), maxSafeInteger)
|
||||
}
|
||||
|
||||
// The length property of the from method is 1.
|
||||
return function from (arrayLike/*, mapFn, thisArg */) {
|
||||
// 1. Let C be the this value.
|
||||
var C = this
|
||||
|
||||
// 2. Let items be ToObject(arrayLike).
|
||||
var items = Object(arrayLike)
|
||||
|
||||
// 3. ReturnIfAbrupt(items).
|
||||
if (arrayLike == null) {
|
||||
throw new TypeError('Array.from requires an array-like object - not null or undefined')
|
||||
}
|
||||
|
||||
// 4. If mapfn is undefined, then let mapping be false.
|
||||
var mapFn = arguments.length > 1 ? arguments[1] : void undefined
|
||||
var T
|
||||
if (typeof mapFn !== 'undefined') {
|
||||
// 5. else
|
||||
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
|
||||
if (!isCallable(mapFn)) {
|
||||
throw new TypeError('Array.from: when provided, the second argument must be a function')
|
||||
}
|
||||
|
||||
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||
if (arguments.length > 2) {
|
||||
T = arguments[2]
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Let lenValue be Get(items, "length").
|
||||
// 11. Let len be ToLength(lenValue).
|
||||
var len = toLength(items.length)
|
||||
|
||||
// 13. If IsConstructor(C) is true, then
|
||||
// 13. a. Let A be the result of calling the [[Construct]] internal method
|
||||
// of C with an argument list containing the single item len.
|
||||
// 14. a. Else, Let A be ArrayCreate(len).
|
||||
var A = isCallable(C) ? Object(new C(len)) : new Array(len)
|
||||
|
||||
// 16. Let k be 0.
|
||||
var k = 0
|
||||
// 17. Repeat, while k < len… (also steps a - h)
|
||||
var kValue
|
||||
while (k < len) {
|
||||
kValue = items[k]
|
||||
if (mapFn) {
|
||||
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k)
|
||||
} else {
|
||||
A[k] = kValue
|
||||
}
|
||||
k += 1
|
||||
}
|
||||
// 18. Let putStatus be Put(A, "length", len, true).
|
||||
A.length = len
|
||||
// 20. Return A.
|
||||
return A
|
||||
}
|
||||
}())
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
@import "global";
|
||||
|
||||
@import "base/base";
|
||||
@import "base/icons";
|
||||
@import "base/animation";
|
||||
|
||||
@import '~vuescroll/dist/vuescroll.css';
|
||||
@import '~katex/dist/katex.min.css';
|
||||
@import '~diff2html/bundles/css/diff2html.min.css';
|
||||
|
||||
@import 'components/codemirror';
|
||||
@import 'components/katex';
|
||||
@import 'components/v-btn';
|
||||
@import 'components/v-data-table';
|
||||
@import 'components/v-dialog';
|
||||
@import 'components/v-form';
|
||||
@import 'components/v-tabs';
|
||||
|
||||
// @import '../libs/twemoji/twemoji-awesome';
|
||||
// @import '../libs/prism/prism.css';
|
||||
// @import '~vue-tour/dist/vue-tour.css';
|
||||
// @import '~xterm/dist/xterm.css';
|
||||
// @import 'node_modules/diff2html/dist/diff2html.min';
|
||||
|
||||
@import 'pages/new';
|
||||
@import 'pages/notfound';
|
||||
@import 'pages/unauthorized';
|
||||
@import 'pages/welcome';
|
||||
@import 'pages/error';
|
||||
|
||||
@import 'layout/_rtl';
|
@ -1,12 +0,0 @@
|
||||
$use-fade: true;
|
||||
$use-zoom: true;
|
||||
$use-bounce: true;
|
||||
|
||||
@import "~animate-sass/animate";
|
||||
|
||||
|
||||
@for $i from 1 to 12 {
|
||||
.wait-p#{$i}s {
|
||||
animation-delay: $i * .1s !important;
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
[v-cloak], .is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
|
||||
&.is-fullscreen {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
.v-application--wrap {
|
||||
transition: all 1.2s ease;
|
||||
transform-origin: 50% 50%;
|
||||
// background-color: #FFF;
|
||||
|
||||
@at-root .theme--dark & {
|
||||
background-color: mc('grey', '900');
|
||||
}
|
||||
}
|
||||
|
||||
#root .v-application {
|
||||
.overline {
|
||||
line-height: 1rem;
|
||||
font-size: .625rem!important;
|
||||
font-weight: 400;
|
||||
letter-spacing: .1666666667em!important;
|
||||
}
|
||||
|
||||
@for $i from 0 through 25 {
|
||||
.radius-#{$i} {
|
||||
border-radius: #{$i}px;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 5 {
|
||||
.grey.darken-2-d#{$i} {
|
||||
background-color: darken(mc('grey', '700'), percentage($i/100)) !important;
|
||||
border-color: darken(mc('grey', '700'), percentage($i/100)) !important;
|
||||
}
|
||||
.grey.darken-2-l#{$i} {
|
||||
background-color: lighten(mc('grey', '700'), percentage($i/100)) !important;
|
||||
border-color: lighten(mc('grey', '700'), percentage($i/100)) !important;
|
||||
}
|
||||
.grey.darken-3-d#{$i} {
|
||||
background-color: darken(mc('grey', '800'), percentage($i/100)) !important;
|
||||
border-color: darken(mc('grey', '800'), percentage($i/100)) !important;
|
||||
}
|
||||
.grey.darken-3-l#{$i} {
|
||||
background-color: lighten(mc('grey', '800'), percentage($i/100)) !important;
|
||||
border-color: lighten(mc('grey', '800'), percentage($i/100)) !important;
|
||||
}
|
||||
.grey.darken-4-d#{$i} {
|
||||
background-color: darken(mc('grey', '900'), percentage($i/100)) !important;
|
||||
border-color: darken(mc('grey', '900'), percentage($i/100)) !important;
|
||||
}
|
||||
.grey.darken-4-l#{$i} {
|
||||
background-color: lighten(mc('grey', '900'), percentage($i/100)) !important;
|
||||
border-color: lighten(mc('grey', '900'), percentage($i/100)) !important;
|
||||
}
|
||||
}
|
||||
.grey.darken-5 {
|
||||
background-color: #0C0C0C !important;
|
||||
border-color: #0C0C0C !important;
|
||||
}
|
||||
|
||||
.blue.darken-5 {
|
||||
background-color: darken(mc('blue', '900'), 20%) !important;
|
||||
border-color: darken(mc('blue', '900'), 20%) !important;
|
||||
}
|
||||
.indigo.darken-5 {
|
||||
background-color: darken(mc('indigo', '900'), 10%) !important;
|
||||
border-color: darken(mc('indigo', '900'), 10%) !important;
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// @font-face {
|
||||
// font-family: 'Material Icons';
|
||||
// font-style: normal;
|
||||
// font-weight: 400;
|
||||
// src: local('Material Icons'),
|
||||
// local('MaterialIcons-Regular'),
|
||||
// url(/fonts/MaterialIcons-Regular.woff2) format('woff2'),
|
||||
// url(/fonts/MaterialIcons-Regular.woff) format('woff');
|
||||
// }
|
||||
|
||||
// .material-icons {
|
||||
// font-family: 'Material Icons', sans-serif;
|
||||
// font-weight: normal;
|
||||
// font-style: normal;
|
||||
// font-size: 24px; /* Preferred icon size */
|
||||
// display: inline-flex;
|
||||
// line-height: 1;
|
||||
// text-transform: none;
|
||||
// letter-spacing: normal;
|
||||
// word-wrap: normal;
|
||||
// white-space: nowrap;
|
||||
// direction: ltr;
|
||||
|
||||
// /* Support for all WebKit browsers. */
|
||||
// -webkit-font-smoothing: antialiased;
|
||||
// /* Support for Safari and Chrome. */
|
||||
// text-rendering: optimizeLegibility;
|
||||
|
||||
// /* Support for Firefox. */
|
||||
// -moz-osx-font-smoothing: grayscale;
|
||||
|
||||
// /* Support for IE. */
|
||||
// font-feature-settings: 'liga';
|
||||
// }
|
||||
|
||||
.icons {
|
||||
display: inline-block;
|
||||
color: mc('grey', '800');
|
||||
&.is-text {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
top: -0.0625em;
|
||||
stroke: none;
|
||||
fill: none;
|
||||
}
|
||||
@each $size in 16,18,20,24,32,48,64,96,128 {
|
||||
&.is-#{$size} {
|
||||
width: #{$size}px;
|
||||
height: #{$size}px;
|
||||
}
|
||||
}
|
||||
&.has-right-pad {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
&.is-outlined {
|
||||
stroke-width: 2px;
|
||||
use {
|
||||
fill: inherit;
|
||||
stroke: mc('grey', '800');
|
||||
}
|
||||
}
|
||||
}
|
||||
.material-design-icon {
|
||||
display: inline-flex;
|
||||
}
|
@ -1,333 +0,0 @@
|
||||
$material-colors: (
|
||||
'red': (
|
||||
'50': #ffebee,
|
||||
'100': #ffcdd2,
|
||||
'200': #ef9a9a,
|
||||
'300': #e57373,
|
||||
'400': #ef5350,
|
||||
'500': #f44336,
|
||||
'600': #e53935,
|
||||
'700': #d32f2f,
|
||||
'800': #c62828,
|
||||
'900': #b71c1c,
|
||||
'a100': #ff8a80,
|
||||
'a200': #ff5252,
|
||||
'a400': #ff1744,
|
||||
'a700': #d50000
|
||||
),
|
||||
|
||||
'pink': (
|
||||
'50': #fce4ec,
|
||||
'100': #f8bbd0,
|
||||
'200': #f48fb1,
|
||||
'300': #f06292,
|
||||
'400': #ec407a,
|
||||
'500': #e91e63,
|
||||
'600': #d81b60,
|
||||
'700': #c2185b,
|
||||
'800': #ad1457,
|
||||
'900': #880e4f,
|
||||
'a100': #ff80ab,
|
||||
'a200': #ff4081,
|
||||
'a400': #f50057,
|
||||
'a700': #c51162
|
||||
),
|
||||
|
||||
'purple': (
|
||||
'50': #f3e5f5,
|
||||
'100': #e1bee7,
|
||||
'200': #ce93d8,
|
||||
'300': #ba68c8,
|
||||
'400': #ab47bc,
|
||||
'500': #9c27b0,
|
||||
'600': #8e24aa,
|
||||
'700': #7b1fa2,
|
||||
'800': #6a1b9a,
|
||||
'900': #4a148c,
|
||||
'a100': #ea80fc,
|
||||
'a200': #e040fb,
|
||||
'a400': #d500f9,
|
||||
'a700': #aa00ff
|
||||
),
|
||||
|
||||
'deep-purple': (
|
||||
'50': #ede7f6,
|
||||
'100': #d1c4e9,
|
||||
'200': #b39ddb,
|
||||
'300': #9575cd,
|
||||
'400': #7e57c2,
|
||||
'500': #673ab7,
|
||||
'600': #5e35b1,
|
||||
'700': #512da8,
|
||||
'800': #4527a0,
|
||||
'900': #311b92,
|
||||
'a100': #b388ff,
|
||||
'a200': #7c4dff,
|
||||
'a400': #651fff,
|
||||
'a700': #6200ea
|
||||
),
|
||||
|
||||
'indigo': (
|
||||
'50': #e8eaf6,
|
||||
'100': #c5cae9,
|
||||
'200': #9fa8da,
|
||||
'300': #7986cb,
|
||||
'400': #5c6bc0,
|
||||
'500': #3f51b5,
|
||||
'600': #3949ab,
|
||||
'700': #303f9f,
|
||||
'800': #283593,
|
||||
'900': #1a237e,
|
||||
'a100': #8c9eff,
|
||||
'a200': #536dfe,
|
||||
'a400': #3d5afe,
|
||||
'a700': #304ffe
|
||||
),
|
||||
|
||||
'blue': (
|
||||
'50': #e3f2fd,
|
||||
'100': #bbdefb,
|
||||
'200': #90caf9,
|
||||
'300': #64b5f6,
|
||||
'400': #42a5f5,
|
||||
'500': #2196f3,
|
||||
'600': #1e88e5,
|
||||
'700': #1976d2,
|
||||
'800': #1565c0,
|
||||
'900': #0d47a1,
|
||||
'a100': #82b1ff,
|
||||
'a200': #448aff,
|
||||
'a400': #2979ff,
|
||||
'a700': #2962ff
|
||||
),
|
||||
|
||||
'light-blue': (
|
||||
'50': #e1f5fe,
|
||||
'100': #b3e5fc,
|
||||
'200': #81d4fa,
|
||||
'300': #4fc3f7,
|
||||
'400': #29b6f6,
|
||||
'500': #03a9f4,
|
||||
'600': #039be5,
|
||||
'700': #0288d1,
|
||||
'800': #0277bd,
|
||||
'900': #01579b,
|
||||
'a100': #80d8ff,
|
||||
'a200': #40c4ff,
|
||||
'a400': #00b0ff,
|
||||
'a700': #0091ea
|
||||
),
|
||||
|
||||
'cyan': (
|
||||
'50': #e0f7fa,
|
||||
'100': #b2ebf2,
|
||||
'200': #80deea,
|
||||
'300': #4dd0e1,
|
||||
'400': #26c6da,
|
||||
'500': #00bcd4,
|
||||
'600': #00acc1,
|
||||
'700': #0097a7,
|
||||
'800': #00838f,
|
||||
'900': #006064,
|
||||
'a100': #84ffff,
|
||||
'a200': #18ffff,
|
||||
'a400': #00e5ff,
|
||||
'a700': #00b8d4
|
||||
),
|
||||
|
||||
'teal': (
|
||||
'50': #e0f2f1,
|
||||
'100': #b2dfdb,
|
||||
'200': #80cbc4,
|
||||
'300': #4db6ac,
|
||||
'400': #26a69a,
|
||||
'500': #009688,
|
||||
'600': #00897b,
|
||||
'700': #00796b,
|
||||
'800': #00695c,
|
||||
'900': #004d40,
|
||||
'a100': #a7ffeb,
|
||||
'a200': #64ffda,
|
||||
'a400': #1de9b6,
|
||||
'a700': #00bfa5
|
||||
),
|
||||
|
||||
'green': (
|
||||
'50': #e8f5e9,
|
||||
'100': #c8e6c9,
|
||||
'200': #a5d6a7,
|
||||
'300': #81c784,
|
||||
'400': #66bb6a,
|
||||
'500': #4caf50,
|
||||
'600': #43a047,
|
||||
'700': #388e3c,
|
||||
'800': #2e7d32,
|
||||
'900': #1b5e20,
|
||||
'a100': #b9f6ca,
|
||||
'a200': #69f0ae,
|
||||
'a400': #00e676,
|
||||
'a700': #00c853
|
||||
),
|
||||
|
||||
'light-green': (
|
||||
'50': #f1f8e9,
|
||||
'100': #dcedc8,
|
||||
'200': #c5e1a5,
|
||||
'300': #aed581,
|
||||
'400': #9ccc65,
|
||||
'500': #8bc34a,
|
||||
'600': #7cb342,
|
||||
'700': #689f38,
|
||||
'800': #558b2f,
|
||||
'900': #33691e,
|
||||
'a100': #ccff90,
|
||||
'a200': #b2ff59,
|
||||
'a400': #76ff03,
|
||||
'a700': #64dd17
|
||||
),
|
||||
|
||||
'lime': (
|
||||
'50': #f9fbe7,
|
||||
'100': #f0f4c3,
|
||||
'200': #e6ee9c,
|
||||
'300': #dce775,
|
||||
'400': #d4e157,
|
||||
'500': #cddc39,
|
||||
'600': #c0ca33,
|
||||
'700': #afb42b,
|
||||
'800': #9e9d24,
|
||||
'900': #827717,
|
||||
'a100': #f4ff81,
|
||||
'a200': #eeff41,
|
||||
'a400': #c6ff00,
|
||||
'a700': #aeea00
|
||||
),
|
||||
|
||||
'yellow': (
|
||||
'50': #fffde7,
|
||||
'100': #fff9c4,
|
||||
'200': #fff59d,
|
||||
'300': #fff176,
|
||||
'400': #ffee58,
|
||||
'500': #ffeb3b,
|
||||
'600': #fdd835,
|
||||
'700': #fbc02d,
|
||||
'800': #f9a825,
|
||||
'900': #f57f17,
|
||||
'a100': #ffff8d,
|
||||
'a200': #ffff00,
|
||||
'a400': #ffea00,
|
||||
'a700': #ffd600
|
||||
),
|
||||
|
||||
'amber': (
|
||||
'50': #fff8e1,
|
||||
'100': #ffecb3,
|
||||
'200': #ffe082,
|
||||
'300': #ffd54f,
|
||||
'400': #ffca28,
|
||||
'500': #ffc107,
|
||||
'600': #ffb300,
|
||||
'700': #ffa000,
|
||||
'800': #ff8f00,
|
||||
'900': #ff6f00,
|
||||
'a100': #ffe57f,
|
||||
'a200': #ffd740,
|
||||
'a400': #ffc400,
|
||||
'a700': #ffab00
|
||||
),
|
||||
|
||||
'orange': (
|
||||
'50': #fff3e0,
|
||||
'100': #ffe0b2,
|
||||
'200': #ffcc80,
|
||||
'300': #ffb74d,
|
||||
'400': #ffa726,
|
||||
'500': #ff9800,
|
||||
'600': #fb8c00,
|
||||
'700': #f57c00,
|
||||
'800': #ef6c00,
|
||||
'900': #e65100,
|
||||
'a100': #ffd180,
|
||||
'a200': #ffab40,
|
||||
'a400': #ff9100,
|
||||
'a700': #ff6d00
|
||||
),
|
||||
|
||||
'deep-orange': (
|
||||
'50': #fbe9e7,
|
||||
'100': #ffccbc,
|
||||
'200': #ffab91,
|
||||
'300': #ff8a65,
|
||||
'400': #ff7043,
|
||||
'500': #ff5722,
|
||||
'600': #f4511e,
|
||||
'700': #e64a19,
|
||||
'800': #d84315,
|
||||
'900': #bf360c,
|
||||
'a100': #ff9e80,
|
||||
'a200': #ff6e40,
|
||||
'a400': #ff3d00,
|
||||
'a700': #dd2c00
|
||||
),
|
||||
|
||||
'brown': (
|
||||
'50': #efebe9,
|
||||
'100': #d7ccc8,
|
||||
'200': #bcaaa4,
|
||||
'300': #a1887f,
|
||||
'400': #8d6e63,
|
||||
'500': #795548,
|
||||
'600': #6d4c41,
|
||||
'700': #5d4037,
|
||||
'800': #4e342e,
|
||||
'900': #3e2723
|
||||
),
|
||||
|
||||
'grey': (
|
||||
'50': #fafafa,
|
||||
'100': #f5f5f5,
|
||||
'200': #eeeeee,
|
||||
'300': #e0e0e0,
|
||||
'400': #bdbdbd,
|
||||
'500': #9e9e9e,
|
||||
'600': #757575,
|
||||
'700': #616161,
|
||||
'800': #424242,
|
||||
'900': #212121
|
||||
),
|
||||
|
||||
'blue-grey': (
|
||||
'50': #eceff1,
|
||||
'100': #cfd8dc,
|
||||
'200': #b0bec5,
|
||||
'300': #90a4ae,
|
||||
'400': #78909c,
|
||||
'500': #607d8b,
|
||||
'600': #546e7a,
|
||||
'700': #455a64,
|
||||
'800': #37474f,
|
||||
'900': #263238,
|
||||
'1000': #11171a
|
||||
),
|
||||
|
||||
'theme': (
|
||||
'primary': #1976D2,
|
||||
'secondary': #424242,
|
||||
'accent': #82B1FF,
|
||||
'error': #FF5252,
|
||||
'info': #2196F3,
|
||||
'success': #4CAF50,
|
||||
'warning': #FFC107
|
||||
)
|
||||
);
|
||||
|
||||
@function mc($color-name, $color-variant: '500') {
|
||||
$color: map-get(map-get($material-colors, $color-name),$color-variant);
|
||||
@if $color {
|
||||
@return $color;
|
||||
} @else {
|
||||
// Libsass still doesn't seem to support @error
|
||||
@warn "=> ERROR: COLOR NOT FOUND! <= | Your $color-name, $color-variant combination did not match any of the values in the $material-colors map.";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue