feat: ckeditor

pull/1017/head
Nick 5 years ago
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'),

@ -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
// ---------------------------------

@ -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",

@ -0,0 +1,6 @@
key: ckeditor
title: Visual Editor
description: Rich-text WYSIWYG Editor
contentType: html
author: requarks.io
props: {}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save