|
|
|
<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 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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
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>
|