wikijs-fork/client/components/editor/editor-ckeditor.vue

254 lines
6.2 KiB
Vue
Raw Normal View History

2019-09-08 16:01:17 +00:00
<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')
2020-04-11 18:51:51 +00:00
page-selector(mode='select', v-model='insertLinkDialog', :open-handler='insertLinkHandler', :path='path', :locale='locale')
2019-09-08 16:01:17 +00:00
</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'
2019-09-08 16:01:17 +00:00
2020-04-11 18:51:51 +00:00
/* global siteLangs */
2019-09-08 16:01:17 +00:00
export default {
components: {
EditorConflict
},
2019-09-08 16:01:17 +00:00
props: {
save: {
type: Function,
default: () => {}
}
},
data() {
return {
editor: null,
stats: {
characters: 0,
words: 0
},
content: '',
2020-04-11 18:51:51 +00:00
isConflict: false,
insertLinkDialog: false
2019-09-08 16:01:17 +00:00
}
},
computed: {
isMobile() {
return this.$vuetify.breakpoint.smAndDown
},
locale: get('page/locale'),
path: get('page/path'),
activeModal: sync('editor/activeModal')
},
methods: {
2020-04-11 18:51:51 +00:00
insertLink () {
this.insertLinkDialog = true
},
insertLinkHandler ({ locale, path }) {
this.editor.execute('link', siteLangs.length > 0 ? `/${locale}/${path}` : `/${path}`)
2020-04-11 18:51:51 +00:00
}
2019-09-08 16:01:17 +00:00
},
async mounted () {
this.$store.set('editor/editorKey', 'ckeditor')
this.editor = await DecoupledEditor.create(this.$refs.editor, {
language: this.locale,
2019-09-08 16:01:17 +00:00
placeholder: 'Type the page content here',
disableNativeSpellChecker: false,
2020-06-23 03:56:18 +00:00
// TODO: Mention autocomplete
//
// mention: {
// feeds: [
// {
// marker: '@',
// feed: [ '@Barney', '@Lily', '@Marshall', '@Robin', '@Ted' ],
// minimumCharacters: 1
// }
// ]
// },
2019-09-08 16:01:17 +00:00
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 }))
2019-09-08 16:01:17 +00:00
}, 300))
this.$root.$on('editorInsert', opts => {
switch (opts.kind) {
case 'IMAGE':
this.editor.execute('imageInsert', {
source: opts.path
})
break
case 'BINARY':
2019-11-17 03:02:51 +00:00
this.editor.execute('link', opts.path, {
linkIsDownloadable: true
2019-09-08 16:01:17 +00:00
})
break
case 'DIAGRAM':
this.editor.execute('imageInsert', {
source: `data:image/svg+xml;base64,${opts.text}`
})
break
2019-09-08 16:01:17 +00:00
}
})
2020-04-11 18:51:51 +00:00
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'))
})
2019-09-08 16:01:17 +00:00
},
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;
}
}
2019-09-08 16:01:17 +00:00
.ck.ck-toolbar {
border: none;
justify-content: center;
background-color: mc('grey', '300');
color: #FFF;
}
.ck.ck-toolbar__items {
justify-content: center;
}
2019-09-08 16:01:17 +00:00
> .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');
}
2019-09-08 16:01:17 +00:00
}
}
}
</style>