RT 我们在使用 ekeditor的时候根据业务需要可能会使用两种不同类型的editor,比如Classsic,document,inline…等等
然后就会报错
CKEditorError:ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated
官网对于这个报错也提出了2种解决方法
原因是因为代码重复度太高了。
如果要在一个页面上加载两个不同的编辑器,则需要确保它们是一起构建的(一次)。这可以通过至少两种方式来实现:
将CKEditor 5从源代码直接集成到您的应用程序中。由于您只构建了一次应用程序,因此您使用的编辑器也将一起构建。
创建CKEditor 5的“超级版本”。您可以创建一个同时导出两个或多个的构建,而不是创建只导出一个编辑器的构建。
第一种就是挨个引用 直接把源码集成到我们项目中。我好奇的翻了一下github的源码。。我直接舍弃掉了
https://github.com/ckeditor/ckeditor5/tree/master/packages
这里不再过多赘述。我们主要讨论第二种
1.首先先克隆官方的代码
git clone -b stable https://github.com/ckeditor/ckeditor5-build-classic.git
npm i
然后把你另外的编辑器也给下载下来 (这里我们以doucument为例)具体类型的编辑器可以在上面的github地址里找到
npm i @ckeditor/ckeditor5-editor-decoupled //这个是document
然后找到src/ckeditor.js
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
// The editor creator to use.
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import UploadAdapter from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter';
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
import Indent from '@ckeditor/ckeditor5-indent/src/indent';
import Link from '@ckeditor/ckeditor5-link/src/link';
import List from '@ckeditor/ckeditor5-list/src/list';
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
import TextTransformation from '@ckeditor/ckeditor5-typing/src/texttransformation';
export default class ClassicEditor extends ClassicEditorBase {}
// Plugins to include in the build.
ClassicEditor.builtinPlugins = [
Essentials,
UploadAdapter,
Autoformat,
Bold,
Italic,
BlockQuote,
CKFinder,
EasyImage,
Heading,
Image,
ImageCaption,
ImageStyle,
ImageToolbar,
ImageUpload,
Indent,
Link,
List,
MediaEmbed,
Paragraph,
PasteFromOffice,
Table,
TableToolbar,
TextTransformation
];
// Editor configuration.
ClassicEditor.defaultConfig = {
toolbar: {
items: [
'heading',
'|',
'bold',
'italic',
'link',
'bulletedList',
'numberedList',
'|',
'indent',
'outdent',
'|',
'imageUpload',
'blockQuote',
'insertTable',
'mediaEmbed',
'undo',
'redo'
]
},
image: {
toolbar: [
'imageStyle:full',
'imageStyle:side',
'|',
'imageTextAlternative'
]
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
language: 'en'
};
从这里我们能看到 export default class ClassicEditor extends ClassicEditorBase {} 他只抛出了这一个东西
所以我们把他重写一下
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import UploadAdapter from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter';
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
import Indent from '@ckeditor/ckeditor5-indent/src/indent';
import Link from '@ckeditor/ckeditor5-link/src/link';
import List from '@ckeditor/ckeditor5-list/src/list';
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
import TextTransformation from '@ckeditor/ckeditor5-typing/src/texttransformation';
import DecoupledEditorBase from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment';
import FontSize from '@ckeditor/ckeditor5-font/src/fontsize';
import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily';
import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor';
import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor';
import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock';
class ClassicEditor extends ClassicEditorBase {}
class DecoupledEditor extends DecoupledEditorBase { }
const plugins = [
// FontSize,
// FontFamily,
// FontColor,
// FontColor,
// FontBackgroundColor,
Essentials,
UploadAdapter,
Autoformat,
Bold,
Italic,
BlockQuote,
CKFinder,
EasyImage,
Heading,
Image,
ImageCaption,
ImageStyle,
ImageToolbar,
ImageUpload,
Indent,
Link,
List,
MediaEmbed,
Paragraph,
PasteFromOffice,
Table,
TableToolbar,
TextTransformation
]
const config = {
toolbar: {
items: [
'heading',
'|',
'bold',
'italic',
'link',
'bulletedList',
'numberedList',
'|',
'indent',
'outdent',
'|',
'imageUpload',
'blockQuote',
'insertTable',
'mediaEmbed',
'undo',
'redo'
]
},
image: {
toolbar: [
'imageStyle:full',
'imageStyle:side',
'|',
'imageTextAlternative'
]
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
language: 'en'
};
const docplugins = [
FontSize,
FontFamily,
FontColor,
FontColor,
FontBackgroundColor,
Underline,
IndentBlock,
Strikethrough,
Alignment,
Essentials,
UploadAdapter,
Autoformat,
Bold,
Italic,
BlockQuote,
CKFinder,
EasyImage,
Heading,
Image,
ImageCaption,
ImageStyle,
ImageToolbar,
ImageUpload,
Indent,
Link,
List,
MediaEmbed,
Paragraph,
PasteFromOffice,
Table,
TableToolbar,
TextTransformation
]
const docconfig = {
toolbar: {
items: [
'heading',
'|',
'fontfamily',
'fontsize',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'underline',
'strikethrough',
'|',
'alignment',
'|',
'numberedList',
'bulletedList',
'|',
'indent',
'outdent',
'|',
'link',
'blockquote',
'imageUpload',
'insertTable',
'mediaEmbed',
'|',
'undo',
'redo'
]
},
image: {
styles: [
'full',
'alignLeft',
'alignRight'
],
toolbar: [
'imageStyle:alignLeft',
'imageStyle:full',
'imageStyle:alignRight',
'|',
'imageTextAlternative'
]
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
language: 'en'
};
ClassicEditor.builtinPlugins = plugins
ClassicEditor.defaultConfig = config
DecoupledEditor.builtinPlugins = docplugins
DecoupledEditor.defaultConfig = docconfig
export default {
ClassicEditor, DecoupledEditor
}
并且将webpack.config.js里的
library: 'ClassicEditor',
换个名字 //随便取
library: 'CKEDITOR',
注意点 在下载包的时候在package.json里检查一下包的版本 如果版本不一致也会报错 这里卡了我很久
都改完就可以打包了
npm run build
打完包可以在sample中的index中检查一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CKEditor 5 – super build</title>
<style>
body {
max-width: 800px;
margin: 20px auto;
}
</style>
</head>
<body>
<h1>CKEditor 5 – super build</h1>
<div id="classic-editor">
<h2>Sample</h2>
<p>This is an instance of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic editor build</a>.</p>
</div>
<div id="inline-editor">
<p>
123
</p>
</div>
<script src="../build/ckeditor.js"></script>
<script>
CKEDITOR.ClassicEditor
.create( document.querySelector( '#classic-editor' ) )
.catch( err => {
console.error( err.stack );
} );
CKEDITOR.DecoupledEditor
.create( document.querySelector( '#inline-editor' ) )
.catch( err => {
console.error( err.stack );
} );
</script>
</body>
</html>
测试的时候在document用时候会没有导航栏 但是在项目中配置完就有 -,-
当一切准备就绪 就可以使用了
我把打包好的build直接把项目中的@ckeditor/ckeditor5-build-decoupled-document中的build给替换掉了
使用classic
<template>
<div class="my-editor" :id="componentId">
<textarea ref="textarea" v-model="currentValue" style="display: none;"></textarea>
</div>
</template>
<script>
/**
* 富文本编辑器
*
*/
import UploadAdapter from './UploadAdapter'
import CKEDITOR from '@ckeditor/ckeditor5-build-decoupled-document'
import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn'
const Toolbars = {
simple: [
'bold',
'italic',
'bulletedList',
'numberedList'
],
classic: [
'heading',
'|',
'bold',
'italic',
'link',
'bulletedList',
'numberedList',
'imageUpload',
'blockQuote',
'insertTable'
],
all: [
'heading',
'|',
'bold',
'italic',
'link',
'bulletedList',
'numberedList',
'imageUpload',
'blockQuote',
'insertTable',
'mediaEmbed',
'undo',
'redo'
]
}
/**
* 上传的文件转换成base64
* @private
* @param loader
* @return {Promise<any>}
*/
function fileToBase64(loader) {
return new Promise((resolve, reject) => {
if (!window.FileReader) {
return reject(new Error('浏览器不支持FileReader'))
}
const reader = new FileReader()
reader.onload = function (e) {
loader.uploadTotal = e.total;
loader.uploaded = e.loaded;
resolve({
default: reader.result
})
}
reader.onerror = function (e) {
reject(e)
}
loader.file.then(file => {
reader.readAsDataURL(file)
})
})
}
export default {
props: {
value: {
type: String,
default: ''
},
toolbar: {
type: [String, Array],
default: 'classic',
validator(val) {
return Array.isArray(val) || ['simple', 'classic', 'all'].includes(val)
}
},
// ckeditor5 配置
config: {
type: Object,
default() {
return {
language: 'zh-cn'
}
}
},
height: {
type: Number
},
// 图片上传方法,需要返回Promise
upload: {
type: Function
},
readonly: Boolean,
disabled: Boolean
},
data() {
this.ckeditor = null
this.styleElement = null
return {
currentValue: this.value
}
},
computed: {
ckeditorConfig() {
return {
toolbar: Toolbars[this.toolbar] || this.toolbar,
...this.config
}
},
componentId() {
return `my-editor-${this._uid}`
}
},
watch: {
value: {
immediate: true,
handler(val) {
this.currentValue = val
}
},
currentValue(val) {
this.$emit('input', val)
/**
* 内容变化时触发
* @event on-change
* @param {string} val 新内容
*/
this.$emit('change', val)
},
height: {
immediate: true,
handler(val) {
val && this.setStyle(val)
}
},
readonly(val) {
if (this.ckeditor) {
this.ckeditor.isReadOnly = val
}
},
disabled(val) {
if (this.ckeditor) {
this.ckeditor.isReadOnly = val
}
}
},
methods: {
init() {
CKEDITOR.ClassicEditor.create(this.$refs.textarea, this.ckeditorConfig)
.then(editor => {
this.ckeditor = editor
this.ckeditor.isReadOnly = this.readonly || this.disabled
this.ckeditor.plugins.get('FileRepository').createUploadAdapter = loader => {
return new UploadAdapter(loader, this.upload || fileToBase64)
}
this.bindEvents(editor)
}).catch(e => {
console.error('init CKEditor error', e)
})
},
bindEvents(editor) {
editor.model.document.on('change:data', () => {
this.currentValue = this.getData()
})
editor.editing.view.document.on('focus', evt => {
this.$emit('focus', evt, editor)
})
editor.editing.view.document.on('blur', evt => {
this.$emit('blur', evt, editor)
})
},
/**
* 获取编辑器内容
* @function getData
* @return {*}
*/
getData() {
if (this.ckeditor) {
return this.ckeditor.getData()
}
return null
},
/**
* 设置编辑器内容
* @function setData
* @param {string} val 文本
*/
setData(val) {
this.currentValue = val
this.ckeditor && this.ckeditor.setData(val)
},
setStyle(height) {
// 由于 ckeditor 没有参数和接口调整编辑器的高度,这里采用在页面加载css来实现设置制定高度
if (!this.styleElement) {
this.styleElement = document.createElement('style')
document.getElementsByTagName('head')[0].appendChild(this.styleElement)
}
this.styleElement.innerText = `#${this.componentId} .ck-content {height: ${height}px; }`
}
},
mounted() {
this.init()
},
beforeDestroy() {
// 销毁样式元素
if (this.styleElement) {
this.styleElement.parentNode.removeChild(this.styleElement)
}
// 销毁 ckeditor
this.ckeditor && this.ckeditor.destroy()
this.ckeditor = null
}
}
</script>
使用 document
<template>
<div class="document-editor">
<div class="document-editor__toolbar">
</div>
<div class="document-editor__editable-container">
<div class="document-editor__editable">
<p>The initial editor data.</p>
</div>
</div>
</div>
</template>
<script>
import CKEDITOR from '@ckeditor/ckeditor5-build-decoupled-document'
import '@ckeditor/ckeditor5-build-decoupled-document/build/translations/zh-cn'
import UploadAdapter from './UploadAdapter'
/**
* 上传的文件转换成base64
* @private
* @param loader
* @return {Promise<any>}
*/
function fileToBase64(loader) {
return new Promise((resolve, reject) => {
if (!window.FileReader) {
return reject(new Error('浏览器不支持FileReader'))
}
const reader = new FileReader()
reader.onload = function (e) {
loader.uploadTotal = e.total;
loader.uploaded = e.loaded;
resolve({
default: reader.result
})
}
reader.onerror = function (e) {
reject(e)
}
loader.file.then(file => {
reader.readAsDataURL(file)
})
})
}
export default {
name: "ckeditor",
props: {
// 图片上传方法,需要返回Promise
uploadImgHook: {
type: Function,
default() {
return () => {
console.error("undefined uploadImg Hook")
}
}
}
},
data(){
return {
editor: null
}
},
methods: {
click(){
console.log(this.editor.getData())
},
init(){
CKEDITOR.DecoupledEditor
.create( document.querySelector( '.document-editor__editable' ), {
simple: [
'bold',
'italic',
'bulletedList',
'numberedList'
],
toolbar: [
'heading',
'|',
'fontfamily',
'fontsize',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'underline',
'strikethrough',
'|',
'alignment',
'|',
'numberedList',
'bulletedList',
'|',
'indent',
'outdent',
'|',
'link',
'blockquote',
'imageUpload',
'insertTable',
'|',
'undo',
'redo'
],
language: "zh-cn",
} )
.then( editor => {
this.editor=editor
editor.plugins.get('FileRepository').createUploadAdapter = loader => {
//let val = editor.getData();
return new UploadAdapter(loader, this.upload || fileToBase64)
};
const toolbarContainer = document.querySelector( '.document-editor__toolbar' );
toolbarContainer.appendChild( editor.ui.view.toolbar.element );
} )
.catch( err => {
console.error( err );
} );
}
},
mounted(){
this.init()
}
}
</script>