chore: purge legacy code

pull/6775/head
NGPixel 3 years ago
parent 9fb9f53d53
commit b979e50830
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -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
}
]
]
}

@ -9,4 +9,11 @@ git config oh-my-zsh.hide-info 1
echo "Waiting for DB container to come online..."
/usr/local/bin/wait-for localhost:5432 -- echo "DB ready"
echo "Installing dependencies..."
cd server
npm install
cd ../ux
npm install
cd ..
echo "Ready!"

@ -1,2 +0,0 @@
save-exact = true
save-prefix = ""

@ -33,7 +33,7 @@
"args": [],
// Set the shell type
"options": {
"cwd": "/workspace"
"cwd": "/workspace/server"
},
// Mark as a background task to avoid the spinner animation on the terminal tab
"isBackground": true,

@ -43,7 +43,6 @@ The current stable release (2.x) is available at https://js.wiki
### Requirements
- Node.js **18.x** or later
- Yarn
- PostgreSQL **11** or later
### Usage
@ -53,18 +52,18 @@ The current stable release (2.x) is available at https://js.wiki
1. Edit `config.yml` and fill in the database details. **You need an empty PostgreSQL database.**
1. Run the following commands to install dependencies and generate the client assets:
```sh
yarn
yarn legacy:build
cd ux
yarn
yarn build
cd server
npm install
cd ../ux
npm install
npm run build
cd ..
```
1. Run this command to start the server:
```sh
node server
```
1. In your browser, navigate to `http://localhost:5000` *(or the IP/hostname of the server and the PORT you defined earlier.)*
1. In your browser, navigate to `http://localhost:3000` *(or the IP/hostname of the server and the PORT you defined earlier.)*
1. Login using the default administrator user:
- Email: `admin@example.com`
- Password: `12345678`
@ -93,17 +92,13 @@ The current stable release (2.x) is available at https://js.wiki
1. Two terminals will launch in split-screen mode at the bottom of the screen. **Server** on the left and **UX** on the right.
1. In the left-side terminal (Server), run the command:
```sh
yarn legacy:build
npm run dev
```
1. In the right-side terminal (UX), run the command:
```sh
yarn build
npm run dev
```
1. Back in the left-side terminal (Server), run the command:
```sh
yarn dev
```
1. Open your browser to `http://localhost:5000`
1. Open your browser to `http://localhost:3000`
1. Login using the default administrator user:
- Email: `admin@example.com`
- Password: `12345678`
@ -115,7 +110,7 @@ The current stable release (2.x) is available at https://js.wiki
From the left-side terminal (Server), run the command:
```sh
yarn dev
npm run dev
```
This will launch the server and automatically restart upon modification of any server files.
@ -124,31 +119,18 @@ Only precompiled client assets are served in this mode. See the sections below o
### Frontend Development (Quasar/Vue 3)
> Make sure you are running `yarn dev` in the left-side terminal (Server) first! Requests still need to be forwarded to the server, even in SPA mode!
> Make sure you are running `npm run dev` in the left-side terminal (Server) first! Requests still need to be forwarded to the server, even in SPA mode!
If you wish to modify any frontend content (under `/ux`), you need to start the Quasar Dev Server in the right-side terminal (UX):
```sh
yarn dev
npm run dev
```
You can then access the site at `http://localhost:5001`. Notice the port being `5001` rather than `5000`. The app runs in a SPA (single-page application) mode and automatically hot-reload any modified component. Any requests made to the `/graphql` endpoint are automatically forwarded to the server running on port `5000`, which is why both must be running at the same time.
Note that not all sections/features are available from this mode, notably the page editing features which still relies on the old client code (Vuetify/Vue 2). For example, trying to edit a page will simply not work. You must use the normal mode (port 5000) to edit pages as it relies on legacy client code. As more features gets ported / developed for Vue 3, they will become available in the SPA mode.
Any change you make to the frontend will not be reflected on port 5000 until you run the command `yarn build` in the right-side terminal.
### Legacy Frontend Development (Vuetify/Vue 2)
Client code from Wiki.js 2.x is located under `/client`. Some sections still rely on this legacy code (notably the page editing features). Code is gradually being removed from this location and replaced with newer code in `/ux`.
In the unlikely event that you need to modify legacy code and regenerate the old client files, you can do so by running in this command in the left-side terminal (Server):
```sh
yarn legacy:build
```
Then run `yarn dev` to start the server again.
### pgAdmin
A web version of pgAdmin (a PostgreSQL administration tool) is available at `http://localhost:8000`. Use the login `dev` / `123123` to login.

@ -8,6 +8,7 @@ If you find such vulnerability, it's important to disclose it in a quick and sec
| Version | Supported |
| ------- | ------------------ |
| 3.x.x | :white_check_mark: |
| 2.x.x | :white_check_mark: |
| 1.x.x | :x: |

@ -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 ![Caption Text](/path/to/image.jpg)
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 &gt; Lorem ipsum
div &gt; dolor sit amet
div &gt; 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>

@ -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,"&amp;").replace(d,"&lt;").replace(e,"&gt;").replace(f,"&para;<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…
Cancel
Save