CKEditor5 经验总结
- 背景
- CKEditor5 简介
- 使用
-
- 基础初始化
- 定义HTML
- 实例
- 自定义图片上传适配器
- 开启图片上传
-
- 图片上传遇到的问题
- 开启视频上传
-
- 视频上传遇到的问题
- 参考博客
背景
项目中 CKEditor4 更新到 CKEditor5(CKEditor4 不支持视频,除升级版本外也可以通过安装插件的方式实现,点击这里),两个版本间的变化很大,且 CKEditor5 没有对应的中文文档以及相关资料较少。
最终通过 CKEditor5 实现富文本的图片、视频上传功能。
本文以 Classic editor 经典编辑器为例,记录在使用过程中遇到的问题。
CKEditor5 简介
CKEditor 5是一个超现代的JavaScript富文本编辑器,具有MVC架构、自定义数据模型和虚拟DOM。它是在ES6中从头编写的,并且有出色的webpack支持。包含经典编辑器、内联编辑器、气球编辑器、气球块编辑器、文档编辑器。
官方提供了四种引入方式:
- CDN
- npm
- Online builder
- Zip download
使用
基础初始化
script>
ClassicEditor
.create( document.querySelector( '#editor' ) )
.catch( error => {
console.error( error );
} );
script>
定义HTML
script src="https://cdn.ckeditor.com/ckeditor5/16.0.0/classic/ckeditor.js">script>
script src="https://cdn.ckeditor.com/ckeditor5/16.0.0/classic/translations/zh-cn.js">script>
form id="thisForm" class="form-horizontal">
div class="row">
div class="form-group">
div class="col-xs-12">
textarea id="adContent">textarea>
div>
div>
div>
form>
实例
script type="text/javascript">
// 初始化内容富文本
var thisAdEditor;
$(function() {
ClassicEditor
.create(document.querySelector("#adContent"), {
language: 'zh-cn', // 中文
extraPlugins: [ MyCustomUploadAdapterPlugin ], // 参考官方文档自定义Adapter实现图片上传
mediaEmbed: { // 提供视频上传支持
previewsInData: true,
providers: [
{
name: 'mediaUpload',
url: [ // 正则表达式根据实际情况修改
/^test01.bintech.com/upload/(w+)/,
/^http://(w+)/,
],
html: match => {
//获取媒体url
const input = match['input'];
return (
'' +
`${input}" ` +
'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' +
'' +
'
'
);
}
}
]
}
})
.then(editor => {
thisAdEditor = editor;
})
.catch(error => {
console.error(error);
});
});
script>
自定义图片上传适配器
官方文档参考:https://ckeditor.com/docs/ckeditor5/latest/framework/deep-dive/upload-adapter.html
script>
class MyUploadAdapter {
constructor( loader ) {
// 上载期间要使用的文件加载器实例
this.loader = loader;
}
// 启动上传过程
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
// 中止上传过程
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
// 初始化 XMLHttpRequest 对象使用URL传递给构造函数
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
// *使用JSON作为数据结构的POST请求,url根据实际情况配置
xhr.open( 'POST', 'admin/upload/rest/files', true );
xhr.responseType = 'json';
}
// 初始化XMLHttpRequest监听器
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `无法上传文件: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
// 当响应上传失败时,调用reject()函数
if ( !response || response.error ) {
return reject( response && response.error ? response.error.message : genericErrorText );
}
// 上传成功的处理
resolve( {
default: response.url
} );
} );
// 编辑器用户界面中显示上传进度条
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
// 准备数据并发送请求
_sendRequest( file ) {
// 准备表单数据
const data = new FormData();
data.append( 'uploadFiles', file );
// 实现认证和CSRF保护等安全机制
// 发送请求.
this.xhr.send( data );
}
}
function MyCustomUploadAdapterPlugin( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
// 配置上传脚本的后端URL
return new MyUploadAdapter( loader );
};
}
script>
开启图片上传
初始化内容富文本时,引入自定义上传Adapter👉extraPlugins: [ MyCustomUploadAdapterPlugin ]。
官方文档提供两种图片上传方案,Official upload adapters 和 Custom upload adapters ,区别在于前者功能更强大但需要付费订阅,参考地址:官方文档参考
图片上传遇到的问题
a、无法上传图片
场景描述: 控制台提示:filerepository-no-upload-adapter
原因: 未定义图片上传适配器
解决方案: 参考官方文档,自定义图片上传适配器,并在初始化ClassicEditor插件时,配置自定义图片上传适配器MyCustomUploadAdapterPlugin官方文档参考
b、403异常
场景描述: 初始化ClassicEditor并配置自定义图片上传适配器,上传图片时提示控制台403异常
原因: 未放开CSRF校验
解决方案: 在Security配置文件中加如下代码,放开请求路径的CSRF保护等安全机制,以避免出现403错误。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Security extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationDetailsSourceHttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource;
@Autowired
private AuthenticationProvider authenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/upload/**").and().headers().frameOptions().sameOrigin().xssProtection().block(true).and();
... // 其他配置
}
}
c、需要注意的细节:
- 图片上传URL根据实际情况修改,xhr.open( ‘POST’, ‘admin/upload/rest/files’, true
); - Controller在定义时,@RequestParam(“uploadFiles”) 要与 data.append(
‘uploadFiles’, file );一致。
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class UploadRestController {
@Autowired
private UploadService uploadService;
@RequestMapping(value = "/admin/upload/rest/files", method = RequestMethod.POST)
public JSON uploadFiles(@RequestParam("uploadFiles") MultipartFile file, HttpServletRequest request,
HttpServletResponse response)
throws IOException {
return uploadService.uploadFiles(request, response, file);
}
}
- 请求返回值格式
成功:
{
"uploaded":true,
"url":"图片地址(如:/upload/xxx/abc.jpg)"
}
失败:
{
"uploaded":false,
"url":
}
开启视频上传
视频上传遇到的问题
a. 视频部分引入媒体url失败
场景描述: 点击工具栏中的视频选项,随便引入一个媒体url,提示不支持该媒体url;
原因: 目前CKEditor5支持的视频provider仅有dailymotion、spotify、youtube、vimeo、instagram、twitter、googleMaps、flickr和facebook,每个provider都有自己的媒体url匹配路径 ,具体可查看meida-embed原文档;
解决方案: 自定义视频provider
在原有js部分中的初始化加入自定义的provider,在url属性中定义自己的url匹配规则,详见上文。
b. 视频预览及页面不显示视频问题
场景描述: 在富文本框贴入视频链接后,视频不能显示;
原因: 当视频链接贴入富文本筐后,插件会自动生成一个如下标签:
figure class="media ck-widget ck-widget_selected" contenteditable="false">
div class="ck-media__wrapper" data-oembed-url="视频访问路径">
...
div>
...
figure>
解决方案: H5 标签
script>
document.querySelectorAll('dev[data-oembed-url]').forEach( element => {
const videoLable = document.createElement('video');
videoLable.setAttribute('src', element.getAttribute('data-oembed-url'));
videoLable.setAttribute('controls', 'controls');
videoLable.setAttribute('style', 'width: 100%; height: 100%;');
element.appendChild(videoLable);
});
script>
参考博客
https://blog.csdn.net/oldpubcat/article/details/105518980
https://blog.csdn.net/huhui806/article/details/105312410