feat: ckeditor
This commit is contained in:
parent
04193dbc6f
commit
7634bd266d
@ -128,7 +128,7 @@ import { get, sync } from 'vuex-pathify'
|
||||
|
||||
import statsQuery from 'gql/admin/dashboard/dashboard-query-stats.gql'
|
||||
|
||||
import adminStore from '@/store/admin'
|
||||
import adminStore from '../store/admin'
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
|
@ -51,7 +51,7 @@ import { Base64 } from 'js-base64'
|
||||
import createPageMutation from 'gql/editor/create.gql'
|
||||
import updatePageMutation from 'gql/editor/update.gql'
|
||||
|
||||
import editorStore from '@/store/editor'
|
||||
import editorStore from '../store/editor'
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
@ -62,6 +62,7 @@ export default {
|
||||
components: {
|
||||
AtomSpinner,
|
||||
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'),
|
||||
editorWysiwyg: () => import(/* webpackChunkName: "editor-wysiwyg", webpackMode: "lazy" */ './editor/editor-wysiwyg.vue'),
|
||||
editorModalEditorselect: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-editorselect.vue'),
|
||||
@ -153,7 +154,7 @@ export default {
|
||||
mounted() {
|
||||
this.$store.set('editor/mode', this.initMode || 'create')
|
||||
|
||||
this.initContentParsed = this.initContent ? Base64.decode(this.initContent) : '# Header\n\nYour content here'
|
||||
this.initContentParsed = this.initContent ? Base64.decode(this.initContent) : ''
|
||||
this.$store.set('editor/content', this.initContentParsed)
|
||||
if (this.mode === 'create') {
|
||||
_.delay(() => {
|
||||
@ -194,7 +195,7 @@ export default {
|
||||
variables: {
|
||||
content: this.$store.get('editor/content'),
|
||||
description: this.$store.get('page/description'),
|
||||
editor: 'markdown',
|
||||
editor: this.$store.get('editor/editorKey'),
|
||||
locale: this.$store.get('page/locale'),
|
||||
isPrivate: false,
|
||||
isPublished: this.$store.get('page/isPublished'),
|
||||
@ -230,7 +231,7 @@ export default {
|
||||
id: this.$store.get('page/id'),
|
||||
content: this.$store.get('editor/content'),
|
||||
description: this.$store.get('page/description'),
|
||||
editor: 'markdown',
|
||||
editor: this.$store.get('editor/editorKey'),
|
||||
locale: this.$store.get('page/locale'),
|
||||
isPrivate: false,
|
||||
isPublished: this.$store.get('page/isPublished'),
|
||||
|
187
client/components/editor/editor-ckeditor.vue
Normal file
187
client/components/editor/editor-ckeditor.vue
Normal file
@ -0,0 +1,187 @@
|
||||
<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 {{stats.characters}} Chars, {{stats.words}} Words
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
import DecoupledEditor from '@requarks/ckeditor5'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
save: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
stats: {
|
||||
characters: 0,
|
||||
words: 0
|
||||
},
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$vuetify.breakpoint.smAndDown
|
||||
},
|
||||
locale: get('page/locale'),
|
||||
path: get('page/path'),
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
async mounted () {
|
||||
this.$store.set('editor/editorKey', 'ckeditor')
|
||||
|
||||
this.editor = await DecoupledEditor.create(this.$refs.editor, {
|
||||
placeholder: 'Type the page content here',
|
||||
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', this.editor.getData())
|
||||
}, 300))
|
||||
|
||||
this.$root.$on('editorInsert', opts => {
|
||||
console.info(opts)
|
||||
switch (opts.kind) {
|
||||
case 'IMAGE':
|
||||
this.editor.execute('imageInsert', {
|
||||
source: opts.path
|
||||
})
|
||||
break
|
||||
case 'BINARY':
|
||||
this.insertAtCursor({
|
||||
content: `[${opts.text}](${opts.path})`
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.ck.ck-toolbar {
|
||||
border: none;
|
||||
justify-content: center;
|
||||
background-color: mc('grey', '300');
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
> .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', '900');
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -296,6 +296,7 @@ export default {
|
||||
},
|
||||
locale: get('page/locale'),
|
||||
path: get('page/path'),
|
||||
mode: get('editor/mode'),
|
||||
activeModal: sync('editor/activeModal')
|
||||
},
|
||||
methods: {
|
||||
@ -456,6 +457,12 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.set('editor/editorKey', 'markdown')
|
||||
|
||||
if (this.mode === 'create') {
|
||||
this.$store.set('editor/content', '# Header\nYour content here')
|
||||
}
|
||||
|
||||
// Initialize CodeMirror
|
||||
|
||||
this.cm = CodeMirror.fromTextArea(this.$refs.cm, {
|
||||
|
@ -48,17 +48,27 @@
|
||||
img(src='/svg/icon-table.svg', alt='Tabular', style='width: 36px;')
|
||||
.body-2.grey--text.mt-2.text--darken-2 Tabular
|
||||
.caption.grey--text.text--darken-1 Excel-like
|
||||
//- v-flex(xs4)
|
||||
//- v-card.radius-7.grey(
|
||||
//- hover
|
||||
//- light
|
||||
//- ripple
|
||||
//- disabled
|
||||
//- )
|
||||
//- v-card-text.text-center(@click='selectEditor("wysiwyg")')
|
||||
//- img(src='/svg/icon-open-in-browser.svg', alt='Visual Builder', style='width: 36px;')
|
||||
//- .body-2.mt-2.grey--text.text--darken-2 Visual Builder
|
||||
//- .caption.grey--text.text--darken-1 Drag-n-drop
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.grey(
|
||||
v-card.radius-7(
|
||||
hover
|
||||
light
|
||||
ripple
|
||||
disabled
|
||||
)
|
||||
v-card-text.text-center(@click='selectEditor("wysiwyg")')
|
||||
img(src='/svg/icon-open-in-browser.svg', alt='Visual Builder', style='width: 36px;')
|
||||
.body-2.mt-2.grey--text.text--darken-2 Visual Builder
|
||||
.caption.grey--text.text--darken-1 Drag-n-drop
|
||||
v-card-text.text-center(@click='selectEditor("ckeditor")')
|
||||
img(src='/svg/icon-open-in-browser.svg', alt='Visual Editor', style='width: 36px;')
|
||||
.body-2.mt-2.primary--text Visual Editor
|
||||
.caption.grey--text Rich-text WYSIWYG
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.grey(
|
||||
hover
|
||||
|
@ -144,7 +144,6 @@
|
||||
:close-on-content-click='false'
|
||||
v-model='isPublishEndShown'
|
||||
:return-value.sync='publishEndDate'
|
||||
full-width
|
||||
width='460px'
|
||||
:disabled='!isPublished || true'
|
||||
)
|
||||
|
@ -2,14 +2,14 @@
|
||||
v-dialog(v-model='isShown', max-width='550')
|
||||
v-card.wiki-form
|
||||
.dialog-header.is-short.is-red
|
||||
v-icon.mr-2(color='white') warning
|
||||
v-icon.mr-2(color='white') mdi-alert
|
||||
span {{$t('editor:unsaved.title')}}
|
||||
v-card-text
|
||||
v-card-text.pt-4
|
||||
.body-2 {{$t('editor:unsaved.body')}}
|
||||
v-card-chin
|
||||
v-spacer
|
||||
v-btn(flat, @click='isShown = false') {{$t('common:actions.cancel')}}
|
||||
v-btn(color='red', @click='discard', dark) {{$t('common:actions.discardChanges')}}
|
||||
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>
|
||||
|
@ -1,6 +1,7 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
|
@ -2,6 +2,7 @@ import { make } from 'vuex-pathify'
|
||||
|
||||
const state = {
|
||||
editor: '',
|
||||
editorKey: '',
|
||||
content: '',
|
||||
mode: 'create',
|
||||
activeModal: '',
|
||||
|
@ -32,7 +32,7 @@
|
||||
padding-right: 3px;
|
||||
|
||||
&::after {
|
||||
font-family: 'Material Design Icons';
|
||||
font-family: 'Material Design Icons', sans-serif;
|
||||
font-size: 24px/1;
|
||||
padding-left: 3px;
|
||||
display: inline-block;
|
||||
@ -605,6 +605,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
figure.image {
|
||||
margin: 1rem 24px 0 24px;
|
||||
|
||||
img {
|
||||
margin: 0 auto;
|
||||
}
|
||||
figcaption {
|
||||
padding: 4px 1rem;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: mc('grey', '700');
|
||||
background-color: mc('grey', '100');
|
||||
|
||||
@at-root .theme--dark & {
|
||||
color: mc('grey', '400');
|
||||
background-color: mc('grey', '800');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// DETAILS
|
||||
// ---------------------------------
|
||||
|
57
package.json
57
package.json
@ -39,10 +39,10 @@
|
||||
"@exlinc/keycloak-passport": "1.0.2",
|
||||
"algoliasearch": "3.34.0",
|
||||
"apollo-fetch": "0.7.0",
|
||||
"apollo-server": "2.9.2",
|
||||
"apollo-server-express": "2.9.2",
|
||||
"apollo-server": "2.9.3",
|
||||
"apollo-server-express": "2.9.3",
|
||||
"auto-load": "3.0.4",
|
||||
"aws-sdk": "2.521.0",
|
||||
"aws-sdk": "2.524.0",
|
||||
"axios": "0.19.0",
|
||||
"azure-search-client": "3.1.5",
|
||||
"bcryptjs-then": "1.0.1",
|
||||
@ -60,7 +60,7 @@
|
||||
"custom-error-instance": "2.1.1",
|
||||
"dependency-graph": "0.8.0",
|
||||
"diff": "4.0.1",
|
||||
"diff2html": "2.11.2",
|
||||
"diff2html": "2.11.3",
|
||||
"dotize": "0.3.0",
|
||||
"elasticsearch6": "npm:@elastic/elasticsearch@6",
|
||||
"elasticsearch7": "npm:@elastic/elasticsearch@7",
|
||||
@ -68,7 +68,7 @@
|
||||
"express": "4.17.1",
|
||||
"express-brute": "1.0.1",
|
||||
"express-session": "1.16.2",
|
||||
"file-type": "12.2.0",
|
||||
"file-type": "12.3.0",
|
||||
"filesize": "4.1.2",
|
||||
"fs-extra": "8.1.0",
|
||||
"getos": "3.1.1",
|
||||
@ -79,7 +79,7 @@
|
||||
"graphql-tools": "4.0.5",
|
||||
"highlight.js": "9.15.10",
|
||||
"i18next": "17.0.13",
|
||||
"i18next-express-middleware": "1.8.1",
|
||||
"i18next-express-middleware": "1.8.2",
|
||||
"i18next-node-fs-backend": "2.1.3",
|
||||
"image-size": "0.7.4",
|
||||
"js-base64": "2.5.1",
|
||||
@ -167,10 +167,10 @@
|
||||
"yargs": "14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.5.0",
|
||||
"@babel/core": "^7.5.4",
|
||||
"@babel/cli": "^7.6.0",
|
||||
"@babel/core": "^7.6.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.4.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.6.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.5.2",
|
||||
"@babel/plugin-proposal-function-sent": "^7.5.0",
|
||||
"@babel/plugin-proposal-json-strings": "^7.2.0",
|
||||
@ -178,21 +178,22 @@
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.2.0",
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
"@babel/preset-env": "^7.5.4",
|
||||
"@mdi/font": "4.2.95",
|
||||
"@babel/polyfill": "^7.6.0",
|
||||
"@babel/preset-env": "^7.6.0",
|
||||
"@mdi/font": "4.3.95",
|
||||
"@panter/vue-i18next": "0.15.1",
|
||||
"@requarks/ckeditor5": "12.4.0-wiki.11",
|
||||
"@vue/babel-preset-app": "3.11.0",
|
||||
"animate-sass": "0.8.2",
|
||||
"animated-number-vue": "1.0.0",
|
||||
"apollo-cache-inmemory": "1.6.3",
|
||||
"apollo-client": "2.6.4",
|
||||
"apollo-link": "1.2.12",
|
||||
"apollo-link-batch-http": "1.2.12",
|
||||
"apollo-link-error": "1.1.11",
|
||||
"apollo-link-http": "1.5.15",
|
||||
"apollo-link": "1.2.13",
|
||||
"apollo-link-batch-http": "1.2.13",
|
||||
"apollo-link-error": "1.1.12",
|
||||
"apollo-link-http": "1.5.16",
|
||||
"apollo-link-persisted-queries": "0.2.2",
|
||||
"apollo-link-ws": "1.0.18",
|
||||
"apollo-link-ws": "1.0.19",
|
||||
"apollo-utilities": "1.3.2",
|
||||
"autoprefixer": "9.6.1",
|
||||
"babel-eslint": "10.0.3",
|
||||
@ -217,20 +218,20 @@
|
||||
"eslint-config-requarks": "1.0.7",
|
||||
"eslint-config-standard": "14.1.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-node": "9.2.0",
|
||||
"eslint-plugin-node": "10.0.0",
|
||||
"eslint-plugin-promise": "4.2.1",
|
||||
"eslint-plugin-standard": "4.0.1",
|
||||
"eslint-plugin-vue": "5.2.3",
|
||||
"fibers": "4.0.1",
|
||||
"file-loader": "4.2.0",
|
||||
"filepond": "4.5.1",
|
||||
"filepond": "4.6.1",
|
||||
"filepond-plugin-file-validate-type": "1.2.4",
|
||||
"filesize.js": "1.0.2",
|
||||
"grapesjs": "0.15.3",
|
||||
"grapesjs": "0.15.5",
|
||||
"graphiql": "0.14.2",
|
||||
"graphql-persisted-document-loader": "1.0.1",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"graphql-voyager": "1.0.0-rc.27",
|
||||
"graphql-voyager": "1.0.0-rc.28",
|
||||
"hammerjs": "2.0.8",
|
||||
"html-webpack-plugin": "4.0.0-beta.8",
|
||||
"html-webpack-pug-plugin": "2.0.0",
|
||||
@ -265,7 +266,7 @@
|
||||
"script-ext-html-webpack-plugin": "2.1.4",
|
||||
"simple-progress-webpack-plugin": "1.1.2",
|
||||
"style-loader": "1.0.0",
|
||||
"terser": "4.2.1",
|
||||
"terser": "4.3.0",
|
||||
"twemoji-awesome": "1.0.6",
|
||||
"url-loader": "2.1.0",
|
||||
"vee-validate": "2.2.15",
|
||||
@ -287,19 +288,19 @@
|
||||
"vue-tour": "1.1.0",
|
||||
"vue2-animate": "2.1.2",
|
||||
"vuedraggable": "2.23.0",
|
||||
"vuescroll": "4.14.0",
|
||||
"vuetify": "2.0.11",
|
||||
"vuescroll": "4.14.3",
|
||||
"vuetify": "2.0.15",
|
||||
"vuetify-loader": "1.3.0",
|
||||
"vuex": "3.1.1",
|
||||
"vuex-pathify": "1.2.4",
|
||||
"vuex-pathify": "1.4.0",
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
"webpack": "4.39.3",
|
||||
"webpack-bundle-analyzer": "3.4.1",
|
||||
"webpack-cli": "3.3.7",
|
||||
"webpack-dev-middleware": "3.7.0",
|
||||
"webpack-cli": "3.3.8",
|
||||
"webpack-dev-middleware": "3.7.1",
|
||||
"webpack-hot-middleware": "2.25.0",
|
||||
"webpack-merge": "4.2.2",
|
||||
"webpack-subresource-integrity": "1.3.2",
|
||||
"webpack-subresource-integrity": "1.3.3",
|
||||
"webpackbar": "4.0.0",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
"write-file-webpack-plugin": "4.5.1",
|
||||
|
6
server/modules/editor/ckeditor/definition.yml
Normal file
6
server/modules/editor/ckeditor/definition.yml
Normal file
@ -0,0 +1,6 @@
|
||||
key: ckeditor
|
||||
title: Visual Editor
|
||||
description: Rich-text WYSIWYG Editor
|
||||
contentType: html
|
||||
author: requarks.io
|
||||
props: {}
|
Loading…
Reference in New Issue
Block a user