首先 在官网 CKEditor 5 Online Builder | Create your own editor in 5 steps 定制你想要的编辑器 包括编辑器的工具栏样式、工具栏的功能插件手动添加;最后选择语言 chiness,下载压缩包到本地得到一个定制的编辑器 如下:
解压缩后初始化插件
npm install
支持 wps 复制图文粘贴到编辑器:
ckeditor5-block-new\node_modules\@ckeditor\ckeditor5-paste-from-office\src\filters
找到 image.js 中 extractImageDataFromRtf 方法 修改如下:添加 if(!images){}
function extractImageDataFromRtf( rtfData ) {
if ( !rtfData ) {
return [];
}
const regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
const regexPicture = new RegExp( '(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g' );
const images = rtfData.match( regexPicture );
const result = [];
// 针对 wps 添加的判断
if (!images) {
regexPictureHeader = /{\\pict[\s\S]+?(\\pngblip-?\d+)?(\\wmetafile8-?\d+)?{\\\*\\blipuid\s?[\da-fA-F]+[\s}]*?/;
regexPicture = new RegExp( '(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g' );
images = rtfData.match( regexPicture );
}
if ( images ) {
for ( const image of images ) {
let imageType = false;
if ( image.includes( '\\pngblip' ) ) {
imageType = 'image/png';
} else if ( image.includes( '\\jpegblip' ) ) {
imageType = 'image/jpeg';
}
if ( imageType ) {
result.push( {
hex: image.replace( regexPictureHeader, '' ).replace( /[^\da-fA-F]/g, '' ),
type: imageType
} );
}
}
}
return result;
}
space.js修改:
export function normalizeSpacing( htmlString ) {
// Run normalizeSafariSpaceSpans() two times to cover nested spans.
return normalizeSafariSpaceSpans( normalizeSafariSpaceSpans( htmlString ) )
// Remove all \r\n from "spacerun spans" so the last replace line doesn't strip all whitespaces.
.replace( /(<span\s+style=['"]mso-spacerun:yes['"]>[^\S\r\n]*?)[\r\n]+([^\S\r\n]*<\/span>)/g, '$1$2' )
.replace( /<span\s+style=['"]mso-spacerun:yes['"]><\/span>/g, '' )
.replace( / <\//g, '\u00A0</' )
.replace( / <o:p><\/o:p>/g, '\u00A0<o:p></o:p>' )
// Remove <o:p> block filler from empty paragraph. Safari uses \u00A0 instead of .
.replace( /<o:p>( |\u00A0)<\/o:p>/g, '' )
// Remove all whitespaces when they contain any \r or \n.
.replace( />([^\S\r\n]*[\r\n]\s*)</g, '><' )
// 针对WPS的修改,去除空格
.replace( />(\s+)</g, '><' );
}
export function normalizeSpacerunSpans( htmlDocument ) {
htmlDocument.querySelectorAll( 'span[style*=spacerun]' ).forEach( el => {
// 针对 wps 添加的判断
if ( el.childNodes[ 0 ] && el.childNodes[ 0 ].data ) {
const innerTextLength = el.innerText.length || 0;
el.innerHTML = Array( innerTextLength + 1 ).join( '\u00A0 ' ).substr( 0, innerTextLength );
}
});
}
在这修改完依赖包中的代码后!本人的做法是:打开webpack.config.js 修改
执行命令 npm run build 打包后的build文件 剪切到 ckeditor5-block-new\node_modules\@ckeditor\ckeditor5-paste-from-office\build
再把webpack文件 恢复原来的代码 再进行打包命令 npm run build,成功后 就是修改后支持粘贴wps 文字+图片的编辑器依赖包了。
正常来说 直接新建一个plugins 文件夹 放入依赖包,然后根据文件地址引用就可以了,后面再说如何把修改代码后的依赖包上传到npm仓库 可以通过npm 直接下载,应用到项目中;
下面先说 图片上传功能:
ckeditor5 的图片上传功能 需要自定义上传适配器,以下是我的功能代码 MyUploadAdapter.js
import {
option, $base_share
} from '../../static/config' //看项目的上传接口是否需要
import { uuid } from "../../utils/commonTool";//看项目的上传接口是否需要
export default class MyUploadAdapter {
constructor( loader ) {
this.loader = loader;
}
// Starts the upload process.
upload() {
return new Promise( ( resolve, reject ) => {
this.loader.file.then( file => {
// Instantiate a FormData object to send the file.
const data = new FormData();
// 上传的参数
data.append('file', file);
data.append("dataId", uuid());
data.append("dataTable", 'sys_base_filepath');
data.append('AccessTimestamp', new Date().getTime())
// 获取 用户的token,按需修改
const vt = window.localStorage.getItem($base_share.ACCESS_TOKEN);
// 使用 fetch 或 XMLHttpRequest 发送文件
fetch( '/apixxx/system/upload', {
method: 'POST',
body: data,
headers:{
// "Content-Type": "multipart/form-data" ,
[$base_share.ACCESS_TOKEN]:vt,
},
})
.then( response => {
if (response.ok) {
console.log('ok')
// 如果响应是 JSON 格式
return response.json();
} else {
// 如果响应状态码不是 2xx,抛出错误
throw new Error('Network response was not ok.');
}
// Handle server response here.
// For example, you can return a URL to the file.
} )
.then(data => {
// 在这里处理解析后的数据
var resData = {}
resData.default = data.result.filePath //获取图片地址
// resData.default ='/api'+ data.result.filePath+"?filepath="+ data.result.bkUrl+"&token="+token //或者接口返回的不是http 而是服务器的文件路径,需要自己去拼接
//注意这里的resData一定要是一个对象,而且外面一定要有一个default设置为图片上传后的url,这是ckeditor的规定格式
resolve(resData);
})
.catch( error => {
console.log(error,'?er')
reject( error );
} );
} );
} );
}
}
在vue页面中的使用
<template>
<div class="content">
<div style="width: 95%;margin: auto;">
<div id="ckeditor-classic" @ready="onEditorReady"
style="width: 100%;min-height: 500px;padding: 20px 30px;box-sizing: border-box;">
</div>
</div>
</div>
</template>
<script>
import Editor from 'zdy-ckeditor5-build-classic'; //我的自定义依赖包
import 'zdy-ckeditor5-build-classic/build/translations/zh' //中文包
import MyUploadAdapter from '../../../assets/js/MyUploadAdapter' //上传图片的js
export default {
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
editor: null, //editor 对象
editorData: '', //内容
title: '',
};
},
watch: {
value: {
handler(newValue) {
if (this.editor) {
// 监听修改回显内容
this.editor.setData(newValue);
this.editorData = newValue
}
}
}
},
mounted() {
this.initEditor();
},
beforeDestroy() {
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
},
methods: {
initEditor() {
Editor
.create(document.querySelector('#ckeditor-classic'), {
language: 'zh-cn',
extraPlugins: [MyUploadAdapter], // 添加上传适配器
mediaEmbed: {
previews: true,
providers: [
// 配置支持的媒体提供商
{ name: 'YouTube', url: /https?:\/\/(www\.)?youtube\.com\// },
{ name: 'Vimeo', url: /https?:\/\/(www\.)?vimeo\.com\// },
],
},
})
.then(editor => {
this.editor = editor;
// editor.setData(this.value);
//监听编辑器内容的变化 发送给父组件;
editor.model.document.on('change:data', () => {
this.editorData = editor.getData();
this.$emit('input', this.editorData); // 发射 input 事件来更新 v-model
});
editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
// You need to return an instance of UploadAdapter.
return new MyUploadAdapter(loader);
};
})
.catch(error => {
console.log(error)
console.error(error.stack);
});
},
// editor
onEditorReady() {
// 确保 'editor' 是一个有效的实例
// editor.ui.viewport.on('paste', (event, data) => {
// // 你的逻辑...
// console.log(data.item,data.item.type,'/====')
// });
}
}
};
</script>
<style lang="scss" scoped>
.content {
width: 100%;
background: #fff;
}
.input {
width: 100%;
height: 40px;
border: none;
font-size: 20px;
}
.input:focus-visible {
outline: none;
}
.ck.ck-editor__editable:not(.ck-editor__nested-editable) {
border-radius: 0;
}
#ckeditor-classic {
height: 300px;
}
/deep/ .ck-blurred.ck.ck-content.ck-editor__editable.ck-rounded-corners.ck-editor__editable_inline {
min-height: 300px !important;
}
/deep/ .ckeditor__editable {
min-height: 300px !important;
}
/deep/.ck-rounded-corners .ck.ck-editor__main>.ck-ediotr__editable {
min-height: 300px !important;
}
/deep/.ck.ck-editor__main>.ck-editor__editable.ck-rounded-corners {
min-height: 300px !important;
}
</style>
以上是目前我涉及到的插件问题解决;下面是把修改代码后的ckeditor5 上传到 npm仓库中去
首先 要在github 创建一个自己的远程仓库,把本地的代码 git init 初始化,以及关联到git 远程仓库去,具体 git 命令:
当你已经创建了一个Git的远程仓库,并且想要上传你的代码到这个仓库中,你可以按照以下步骤操作:
初始化本地仓库: 如果你的本地项目目录还没有被Git跟踪,你需要首先在项目目录中运行
git init
来初始化一个新的Git仓库。添加远程仓库: 将远程仓库的URL添加到你的本地仓库中。你需要使用
git remote add
命令,例如:
git remote add origin <远程仓库的URL>
这里的
<远程仓库的URL>
应该替换成你的远程仓库的实际URL。添加文件到本地仓库: 使用
git add
命令将你的文件添加到本地仓库的暂存区。你可以添加单个文件或整个目录:
git add <文件名> # 或者 git add .
提交更改: 使用
git commit
命令将暂存区的更改提交到本地仓库:
git commit -m "你的提交信息"
提交信息应该简短描述你所做的更改。
推送到远程仓库: 使用
git push
命令将你的提交推送到远程仓库。如果你是第一次推送到远程仓库,可能需要进行身份验证:
git push -u origin <分支名>
这里的
<分支名>
通常是master
或main
,取决于你的远程仓库的默认分支名称。
打开git bash here
执行命令:
npm init 初始化创建你的npm 仓库,其中name : 你的插件名称(npm install 时的插件名字);需要复制你的git远程仓库地址;
package.json 中 删掉 private 字段****否则不被发布到npm仓库;每次更新包后,都需要手动修改version 版本号,否则上传包 失败
npm init
命令用于初始化一个新的Node.js项目,并创建一个package.json
文件,该文件包含了项目的元数据和依赖信息。当你运行npm init
时,npm会提示你输入一些信息来填充package.json
文件。下面是一些常见的字段及其意义:
name: 项目的名称,通常遵循
/
的格式,例如@my-scope/my-package
。如果你没有注册npm scope,可以只填写项目名称。version: 项目的当前版本,遵循语义版本控制(Semantic Versioning),格式为
主版本号.次版本号.修订号
,例如1.0.0
。description: 项目的简短描述。
entry point(或
main
): 项目的入口文件,通常是你的模块的默认导出文件,例如index.js
。repository: 项目的代码仓库URL,可以是Git仓库。
test command: 运行测试的命令,例如
"test": "echo \"Error: no test specified\" && exit 1"
。git repository: Git仓库的URL,用于关联npm包和代码仓库。
keywords: 与项目相关的关键词,有助于npm搜索和用户发现。
author: 项目作者的姓名。
license: 项目的许可证,决定了其他人如何使用、修改和分发你的代码。常见的许可证包括MIT、ISC、Apache 2.0等。
files: 指定要包含在npm包中的文件列表。
bin: 指定命令行工具的入口点,允许你将JavaScript文件作为命令行工具发布。
scripts: 定义可以运行的脚本命令,例如
"start": "node index.js"
。dependencies: 项目运行时依赖的包列表。
devDependencies: 仅在开发时需要的包列表,例如测试框架。
peerDependencies: 定义对项目运行时依赖的包的版本要求,通常用于插件或库。
engines: 指定项目运行所需的Node.js版本范围。
os / cpu: 指定项目运行所需的操作系统或CPU架构。
private: 如果设置为
true
,表示这个包不应该被发布到npm公共仓库。*************(重要)当你运行
npm init
时,你可以选择回答这些问题,或者简单地按回车键接受默认值。如果你选择跳过某些字段,可以在任何时候手动编辑生成的package.json
文件。此外,你还可以使用-y
或--yes
标志来自动接受所有默认值并创建package.json
文件。
init后 执行npm login 登录你的npm 账号;登录成功后 npm publish 推送你的插件包到仓库;成功后就可以在项目中 通过npm install xxxx 下载并使用啦;如果项目是vue3 的 可以直接通过pnpm 下载 按照插件;
注:下载自定义插件后,就不需要再执行npm install @ckeditor/ckeditor5-build-classic 等等了;
后续如果再添加功能,后续再更新;欢迎一起学习探讨 ckeditor5 的使用
后续问题:段落缩进无法使用以及标签样式丢失问题解决:
段落缩进按钮功能一直置灰禁用的状态,无法点击,需要在ckeditor.js 中添加IndentBlock
重新npm run build 打包插件后生效
粘贴到编辑器的内容 html标签内联样式丢失问题解决:
添加GeneralHtmlSupport 插件在ckeditor.js中
import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
import { GeneralHtmlSupport } from '@ckeditor/ckeditor5-html-support';
import { Alignment } from '@ckeditor/ckeditor5-alignment';
import { Autoformat } from '@ckeditor/ckeditor5-autoformat';
import { Bold, Code, Italic, Underline } from '@ckeditor/ckeditor5-basic-styles';
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
import { CKBox, CKBoxImageEdit } from '@ckeditor/ckeditor5-ckbox';
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
import { CodeBlock } from '@ckeditor/ckeditor5-code-block';
import type { EditorConfig } from '@ckeditor/ckeditor5-core';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { FontBackgroundColor, FontColor, FontFamily, FontSize } from '@ckeditor/ckeditor5-font';
import { Heading } from '@ckeditor/ckeditor5-heading';
import { Highlight } from '@ckeditor/ckeditor5-highlight';
重新npm run build 打包插件;注意在package.json 添加GeneralHtmlSupport
在vue页面 初始化editor实例中
initEditor() {
Editor
.create(document.querySelector('#ckeditor-classic'), {
language: 'zh-cn',
extraPlugins: [MyUploadAdapter], // 添加上传图片适配器
fontSize: {
options: ['12px', '14px', '16px', '18px', '20px','22px','24px','28px','32px']
},
//允许粘贴携带样式
htmlSupport: {
allow: [//哪些功能被支持
{
name: /.*/,//需要启用的元素名称
attributes: true,
classes: true,//允许所有标签的样式
styles: true,//允许的内联样式
}
],
disallow:[//不支持哪些功能
{name:'script'},//不允许创建script元素
{
name:/.*/,//不允许所有元素执行点击事件
attributes:['onclick'],
}
]
},
//字数回调
wordCount: {
onUpdate: stats => {
this.characters = stats.characters
// Prints the current content statistics.
// console.log( `Characters: ${ stats.characters }\nWords: ${ stats.words }` );
}
},
mediaEmbed: {
previews: true,
providers: [
// 配置支持的媒体提供商
{ name: 'YouTube', url: /https?:\/\/(www\.)?youtube\.com\// },
{ name: 'Vimeo', url: /https?:\/\/(www\.)?vimeo\.com\// },
],
},
})
.then(editor => {
this.editor = editor;
editor.model.document.on('change:data', () => {
this.editorData = editor.getData();
this.$emit('input', this.editorData); // 发射 input 事件来更新 v-model
});
editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
// You need to return an instance of UploadAdapter.
return new MyUploadAdapter(loader);
};
})
.catch(error => {
console.log(error)
console.error(error.stack);
});
},
隐藏右下角的logo
<style>
.ck.ck-balloon-panel.ck-balloon-panel_visible {
display: none !important;
}
</style>
设置展示当前输入的字数:
<template>
<div class="content">
<div id="ckeditor-classic" @ready="onEditorReady"
style="width: 100%;min-height: 500px;padding: 20px 10px;box-sizing: border-box;">
</div>
//这是字数的容器和数据
<div class="ck ck-word-count">
<!-- 行数 -->
<!-- <div class="ck-word-count__words">Words: %%</div> -->
<div class="ck-word-count__characters">字数:{{ characters }} </div>
</div>
</div>
</template>
//设置样式
<style>
.ck.ck-balloon-panel.ck-balloon-panel_visible {
display: none !important;
}
.ck-word-count{
justify-content: flex-end;
display: flex;
background:#fff;
padding:calc(0.6em*0.5) 0.6em;
border: 1px solid #ccced1;
border-top-width: 0;
border-radius:0 0 2px;
font: normal normal normal 13px / 1.84615 / Helvetica,Arial,Tahoma,Verdana,Sans-Serif
}
</style>
效果图: