react使用CKEditor5,两种使用方式(在线构建器和从源代码构建)。附国际化
介绍
介绍:功能丰富,是最好用的富文本编辑器。
特点:CKEditor5是基于Nodejs的编译器,很好地支持了React等主流web框架,它把菜单的每一项功能都进行了拆分,通过插件的方式来引入,最后配置好。
两种使用方式:
- CKEditor5在线构建器构建。构建好的版本使用,直接使用,无需配置;但是功能简单,不可配置
- 从源代码构建CKEditor5。CKEditor5 源码自行打包构建,可以配置丰富的功能,配置折腾!
ps:以下详解采用 从源代码构建CKEditor5 编辑器
一、CKEditor5在线构建器构建
说明:该部分内容即官方文档中的 快速开始。(可从文档直接查看 快速开始)
优点:CKEditor5在线构建器 快速上手,直接使用,无需配置;
缺点:无需配置,不能配置工具栏。
快速上手
- 安装CKEditor5
pnpm install --save @ckeditor/ckeditor5-react @ckeditor/ckeditor5-build-classic
- 在React组件中使用CKEditor5组件
// App.jsx / App.tsx
import React, { Component } from 'react';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
class App extends Component {
render() {
return (
<div className="App">
<h2>Using CKEditor 5 build in React</h2>
<CKEditor
editor={ ClassicEditor }
data="<p>Hello from CKEditor 5!</p>"
onReady={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event ) => {
console.log( event );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
</div>
);
}
}
export default App;
二、从源代码构建CKEditor5(使用)
说明:这里不能直接使用@ckeditor/ckeditor5-build-classic这个包了,因为这个包已经包含了很多插件,加入代码编辑器插件会与其冲突,所以只能用@ckeditor/ckeditor5-editor-classic。
优点:从源代码构建CKEditor5 可以单独配置工具栏,每项功能可以单独配置,功能丰富;
缺点-配置难受:①工具栏每项均需安装对应插件;②根据不同的项目脚手架,需要采取不同的环境配置。
1. 安装CKEditor5 依赖项
相对比较全的一份依赖名单如下:(注:如需配置其他功能再单独安装对应依赖即可)
pnpm i @ckeditor/ckeditor5-react @ckeditor/ckeditor5-editor-classic
// 安装插件,用于配置工具栏功能
@ckeditor/ckeditor5-alignment
@ckeditor/ckeditor5-block-quote
@ckeditor/ckeditor5-cloud-services
@ckeditor/ckeditor5-code-block
@ckeditor/ckeditor5-essentials
@ckeditor/ckeditor5-export-pdf
@ckeditor/ckeditor5-export-word
@ckeditor/ckeditor5-find-and-replace
@ckeditor/ckeditor5-font
@ckeditor/ckeditor5-html-support
@ckeditor/ckeditor5-heading
@ckeditor/ckeditor5-highlight
@ckeditor/ckeditor5-horizontal-line
@ckeditor/ckeditor5-html-embed
@ckeditor/ckeditor5-image
@ckeditor/ckeditor5-indent
@ckeditor/ckeditor5-link
@ckeditor/ckeditor5-list
@ckeditor/ckeditor5-media-embed
@ckeditor/ckeditor5-mention
@ckeditor/ckeditor5-page-break
@ckeditor/ckeditor5-paste-from-office
@ckeditor/ckeditor5-remove-format
@ckeditor/ckeditor5-show-blocks
@ckeditor/ckeditor5-source-editing
@ckeditor/ckeditor5-special-characters
@ckeditor/ckeditor5-style
@ckeditor/ckeditor5-typing
@ckeditor/ckeditor5-word-count
@ckeditor/ckeditor5-upload
2. 环境配置(vite/webpack)
根据创建项目的方式选择其中对应的环境配置,以下介绍三种创建项目配置,本人均搭建demo使用成功
react+vite项目
使用 React 和 Vite 配置 CKEditor 5 很简单。通过导入ckeditor5并将其添加到插件列表来修改现有配置。
安装依赖:pnpm i @ckeditor/ckeditor5-theme-lark
配置vite.config.ts
// vite.config.js / vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import ckeditor5 from '@ckeditor/vite-plugin-ckeditor5';
export default defineConfig( {
plugins: [
react(),
ckeditor5( { theme: require.resolve( '@ckeditor/ckeditor5-theme-lark' ) } )
],
} )
create-react-app项目
修改webpack配置,配置直接参考官方文档 Create React App
umi4项目(复杂)
安装依赖
pnpm i -D postcss-loader style-loader css-loader
修改.umirc.ts配置
// .umirc.ts / config/config.ts
chainWebpack(config) {
const { styles } = require('@ckeditor/ckeditor5-dev-utils');
const svgReg = /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/;
const cssReg = /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/;
config.module.rule('cke5-svg').test(svgReg).type(
// raw-loader
'asset/source',
);
// svg exclude
['svg', 'svgr'].forEach((rule) => {
config.module.rule(rule).exclude.add(svgReg);
});
// css rule
config.module
.rule('cke5-css')
.test(cssReg)
.use('style-loader')
.loader(require.resolve('style-loader'))
.end()
.use('css-loader')
.loader(require.resolve('css-loader'))
.end()
.use('postcss-loader')
.loader(require.resolve('postcss-loader'))
.options({
postcssOptions: styles.getPostCssConfig({
themeImporter: {
themePath: require.resolve('@ckeditor/ckeditor5-theme-lark'),
},
minify: true,
}),
});
// css exclude
config.module.rule('css').exclude.add(cssReg);
// assets exclude
config.module
.rule('asset')
.oneOf('fallback')
.exclude.add(svgReg)
.add(cssReg);
},
3. 编辑器配置(组件)
Editor组件,编辑器配置(config:toolbar配置工具栏项,plugins使用对应toolbar项。可以增改尝试)
需另外使用其他细节的配置请查阅官方文档进行配置
import { CKEditor } from '@ckeditor/ckeditor5-react';
import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
// NOTE: Use the editor from source (not a build)!
// 引入配置工具栏插件
import { Alignment } from '@ckeditor/ckeditor5-alignment';
import {
Bold,
Code,
Italic,
Strikethrough,
Subscript,
Superscript,
Underline,
} from '@ckeditor/ckeditor5-basic-styles';
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
import { CodeBlock } from '@ckeditor/ckeditor5-code-block';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { ExportPdf } from '@ckeditor/ckeditor5-export-pdf';
import { ExportWord } from '@ckeditor/ckeditor5-export-word';
import { FindAndReplace } from '@ckeditor/ckeditor5-find-and-replace';
import { Font } from '@ckeditor/ckeditor5-font';
import { Heading } from '@ckeditor/ckeditor5-heading';
import { Highlight } from '@ckeditor/ckeditor5-highlight';
import { HorizontalLine } from '@ckeditor/ckeditor5-horizontal-line';
import { HtmlEmbed } from '@ckeditor/ckeditor5-html-embed';
import { GeneralHtmlSupport } from '@ckeditor/ckeditor5-html-support';
import {
AutoImage,
Image,
ImageCaption,
ImageInsert,
ImageResize,
ImageStyle,
ImageToolbar,
ImageUpload,
PictureEditing,
} from '@ckeditor/ckeditor5-image';
import { Indent, IndentBlock } from '@ckeditor/ckeditor5-indent';
import { AutoLink, Link, LinkImage } from '@ckeditor/ckeditor5-link';
import { List, ListProperties, TodoList } from '@ckeditor/ckeditor5-list';
import { MediaEmbed } from '@ckeditor/ckeditor5-media-embed';
import { Mention } from '@ckeditor/ckeditor5-mention';
import { PageBreak } from '@ckeditor/ckeditor5-page-break';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office';
import { RemoveFormat } from '@ckeditor/ckeditor5-remove-format';
import { ShowBlocks } from '@ckeditor/ckeditor5-show-blocks';
import { SourceEditing } from '@ckeditor/ckeditor5-source-editing';
import {
SpecialCharacters,
SpecialCharactersEssentials,
} from '@ckeditor/ckeditor5-special-characters';
import { Style } from '@ckeditor/ckeditor5-style';
import {
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
} from '@ckeditor/ckeditor5-table';
import { TextTransformation } from '@ckeditor/ckeditor5-typing';
import { SimpleUploadAdapter } from '@ckeditor/ckeditor5-upload';
import { WordCount } from '@ckeditor/ckeditor5-word-count';
// 工具栏功能项配置
const editorConfig: any = {
plugins: [
SimpleUploadAdapter,
BlockQuote,
Bold,
Heading,
Image,
ImageCaption,
ImageStyle,
ImageToolbar,
Indent,
Italic,
Link,
List,
MediaEmbed,
Paragraph,
Table,
TableToolbar,
Alignment,
AutoImage,
AutoLink,
CloudServices,
Code,
CodeBlock,
Essentials,
ExportPdf,
ExportWord,
FindAndReplace,
Font,
Highlight,
HorizontalLine,
HtmlEmbed,
ImageInsert,
ImageResize,
ImageUpload,
IndentBlock,
GeneralHtmlSupport,
LinkImage,
ListProperties,
TodoList,
Mention,
PageBreak,
PasteFromOffice,
PictureEditing,
RemoveFormat,
ShowBlocks,
SourceEditing,
SpecialCharacters,
SpecialCharactersEssentials,
Style,
Strikethrough,
Subscript,
Superscript,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TextTransformation,
Underline,
WordCount,
],
toolbar: {
items: [
'undo',
'redo',
'heading',
'fontSize',
'fontColor',
'fontBackgroundColor',
'bold',
'underline',
'italic',
{
label: 'Formatting',
icon: '',
items: [
'strikethrough',
'subscript',
'superscript',
'code',
'horizontalLine',
'|',
'removeFormat',
],
},
'alignment',
'bulletedList',
'numberedList',
'outdent',
'indent',
'link',
'insertImage',
'insertTable',
// '-', // break point
{
label: 'Others',
icon: '',
items: [
'todoList',
'specialCharacters',
'pageBreak',
{
label: 'Insert',
icon: '',
items: ['highlight', 'blockQuote', 'mediaEmbed', 'codeBlock', 'htmlEmbed'],
},
'fontFamily',
'exportPdf',
'exportWord',
'showBlocks',
'findAndReplace',
'selectAll',
'|',
'sourceEditing',
],
},
],
// shouldNotGroupWhenFull: true,
},
// language: 'zh-cn', // 需引入语言包,进行设置。
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true,
},
],
},
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph',
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1',
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2',
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3',
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4',
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5',
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6',
},
],
},
exportPdf: {
stylesheets: ['EDITOR_STYLES'],
fileName: 'export-pdf-demo.pdf',
converterOptions: {
format: 'Tabloid',
margin_top: '20mm',
margin_bottom: '20mm',
margin_right: '24mm',
margin_left: '24mm',
page_orientation: 'portrait',
},
},
exportWord: {
stylesheets: ['EDITOR_STYLES'],
fileName: 'export-word-demo.docx',
converterOptions: {
format: 'B4',
margin_top: '20mm',
margin_bottom: '20mm',
margin_right: '12mm',
margin_left: '12mm',
page_orientation: 'portrait',
},
},
// fontFamily: {
// options: [
// 'default',
// 'Blackoak Std',
// '宋体,SimSun',
// '新宋体,NSimSun',
// '黑体,SimHei',
// '微软雅黑,Microsoft YaHei',
// '楷体_GB2312,KaiTi_GB2312',
// '隶书,LiSu',
// '幼园,YouYuan',
// '华文细黑,STXihei',
// '细明体,MingLiU',
// '新细明体,PMingLiU',
// ],
// },
fontSize: {
options: [10, 12, 14, 'default', 18, 20, 22, 28],
supportAllValues: true,
},
htmlEmbed: {
showPreviews: true,
},
image: {
styles: ['alignCenter', 'alignLeft', 'alignRight'],
resizeOptions: [
{
name: 'resizeImage:original',
label: 'Original',
value: null,
},
{
name: 'resizeImage:50',
label: '50%',
value: '50',
},
{
name: 'resizeImage:75',
label: '75%',
value: '75',
},
],
toolbar: [
'imageTextAlternative',
'toggleImageCaption',
'|',
'imageStyle:inline',
'imageStyle:wrapText',
'imageStyle:breakText',
'|',
'resizeImage',
'|',
],
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true,
},
},
link: {
decorators: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file',
},
},
},
},
mention: {
feeds: [
{
marker: '@',
feed: [
'@apple',
'@bears',
'@brownie',
'@cake',
'@cake',
'@candy',
'@canes',
'@chocolate',
'@cookie',
'@cotton',
'@cream',
'@cupcake',
'@danish',
'@donut',
'@dragée',
'@fruitcake',
'@gingerbread',
'@gummi',
'@ice',
'@jelly-o',
'@liquorice',
'@macaroon',
'@marzipan',
'@oat',
'@pie',
'@plum',
'@pudding',
'@sesame',
'@snaps',
'@soufflé',
'@sugar',
'@sweet',
'@topping',
'@wafer',
],
minimumCharacters: 1,
},
],
},
// 图片上传.简单的上传适配器 功能配置
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/',
// https://ckeditor.com/docs/ckeditor5/latest/assets/img/malta.jpg
// Enable the XMLHttpRequest.withCredentials property.
withCredentials: true,
// Headers sent along with the XMLHttpRequest to the upload server.
headers: {
'X-CSRF-TOKEN': 'CSRF-Token',
Authorization: 'Bearer <JSON Web Token>',
},
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells',
'tableProperties',
'tableCellProperties',
'toggleTableCaption',
],
},
// licenseKey: "your-license-key",
};
interface Props {
html: string;
handleSetHtml: (editor: any) => void;
}
export default function App({ html, handleSetHtml }: Props) {
return (
<div className="ckeditor">
<CKEditor
editor={ClassicEditor}
config={editorConfig}
data={html}
// onReady={(editor) => {
// editor.setData(html);
// }}
onChange={(_event, editor) => {
const data = editor.getData();
handleSetHtml(data);
}}
/>
</div>
);
}
4. 在React组件中使用CKEditor5组件
在React组件中使用
// 引入组件
import Editor from "xxx"
// 使用组件
<Editor html={html} handleSetHtml={handleSetHtml} />
// 编辑器内容,和设置内容
const [html, setHtml] = useState('');
const handleSetHtml = (editor: string) => {
setHtml(editor);
};
5. 样式修改
global.less
// 富文本
.ck-content {
min-height: 300px;
ul,
ol {
margin-left: 40px;
list-style-position: outside;
}
}
.ck-powered-by {
display: none;
}
.ckeditor {
width: 100%;
}
总结&问题
参考:对 UmiJS 和 ckeditor 的折腾,CKEditor5的入门使用(react)-国家化
依赖问题
- 推荐使用pnpm安装依赖,yarn安装可能出现问题
- 单独安装依赖,如出现问题,本人使用的方式是删除依赖,重新
pnpm i
- 大部分依赖插件需要安装同一版本,否则可能出现意外的问题
附:项目中package.js使用的依赖版本
// dependencies
"@ckeditor/ckeditor5-alignment": "^41.3.1",
"@ckeditor/ckeditor5-basic-styles": "^41.0.1",
"@ckeditor/ckeditor5-block-quote": "^41.3.1",
"@ckeditor/ckeditor5-cloud-services": "^41.3.1",
"@ckeditor/ckeditor5-code-block": "^41.3.1",
"@ckeditor/ckeditor5-dev-translations": "^39.6.3",
"@ckeditor/ckeditor5-dev-utils": "^38.3.1",
"@ckeditor/ckeditor5-editor-classic": "^41.0.1",
"@ckeditor/ckeditor5-essentials": "^41.3.1",
"@ckeditor/ckeditor5-export-pdf": "^41.3.1",
"@ckeditor/ckeditor5-export-word": "^41.3.1",
"@ckeditor/ckeditor5-find-and-replace": "^41.3.1",
"@ckeditor/ckeditor5-font": "^41.3.1",
"@ckeditor/ckeditor5-heading": "^41.3.1",
"@ckeditor/ckeditor5-highlight": "^41.3.1",
"@ckeditor/ckeditor5-horizontal-line": "^41.3.1",
"@ckeditor/ckeditor5-html-embed": "^41.3.1",
"@ckeditor/ckeditor5-html-support": "^41.3.1",
"@ckeditor/ckeditor5-image": "^41.3.1",
"@ckeditor/ckeditor5-indent": "^41.3.1",
"@ckeditor/ckeditor5-link": "^41.3.1",
"@ckeditor/ckeditor5-list": "^41.3.1",
"@ckeditor/ckeditor5-media-embed": "^41.3.1",
"@ckeditor/ckeditor5-mention": "^41.3.1",
"@ckeditor/ckeditor5-page-break": "^41.3.1",
"@ckeditor/ckeditor5-paragraph": "^41.0.1",
"@ckeditor/ckeditor5-paste-from-office": "^41.3.1",
"@ckeditor/ckeditor5-react": "^6.1.0",
"@ckeditor/ckeditor5-remove-format": "^41.3.1",
"@ckeditor/ckeditor5-show-blocks": "^41.3.1",
"@ckeditor/ckeditor5-source-editing": "^41.3.1",
"@ckeditor/ckeditor5-special-characters": "^41.3.1",
"@ckeditor/ckeditor5-style": "^41.3.1",
"@ckeditor/ckeditor5-table": "^41.0.1",
"@ckeditor/ckeditor5-theme-lark": "^41.0.1",
"@ckeditor/ckeditor5-typing": "^41.3.1",
"@ckeditor/ckeditor5-upload": "^41.3.1",
"@ckeditor/ckeditor5-word-count": "^41.3.1",
国际化
文档:本地化
注:以下两种方式翻译,自己配置toolbar项的label的(即自己配置的项-下拉选) 需要单独手动翻译,可使用如开发admin/email项目中的i18n国际化方式($t(),这里就不作介绍)。
两种国际化方式
方式1、设置UI语言
说明:这种方式设置语言,一些toolbar项会存在漏翻译的情况。部分需要自定义翻译(根据导入的语言)
当使用预定义版本之一或在线构建器构建的编辑器,需要先导入翻译
// Import translations for the German language.
import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn';
import '@ckeditor/ckeditor5-build-classic/build/translations/zh';
在组件中配置编辑器的语言
<CKEditor
config={ {
// Use the zh-cn language for this editor.
language: 'zh-cn',
// ...
} }
editor={ ClassicEditor }
data="<p>Hello from CKEditor 5!</p>"
/>
方式2、使用特定语言构建编辑器
说明:这种方式设置语言,翻译全面,默认的toolbar项都可以翻译,不可重新更改。
注:如通过导入翻译重新更改语言则如方法一,此种方式即没意义了。
安装依赖
pnpm install --save @ckeditor/ckeditor5-dev-translations
添加到 webpack 配置中
// webpack.config.js
const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );
module.exports = {
// ...
plugins: [
// ....
new CKEditorTranslationsPlugin( {
// The UI language. Language codes follow the https://en.wikipedia.org/wiki/ISO_639-1 format.
language: 'zh-cn',
addMainLanguageTranslationsToAllAssets: true
} ),
// ....
],
// ...
};
项目中使用
包括 **部分需要自定义翻译 **如下
编辑器配置(组件)
在编辑器组件中添加以下内容即可设置ckeditor5语言
// ...
// 引入语言包,获取项目设置的语言
import '@/locales/editor/zh';
import '@/locales/editor/zh-cn';
import { useAppSelector } from '@/hooks/useAppHooks';
import { selectLanguage } from '@/store/reducer/langSlice';
const langConfig = {
en: 'en',
hk: 'zh',
cn: 'zh-cn',
};
// 获取设置的语言
const lang = useAppSelector(selectLanguage);
// map语言并设置
editorConfig.language = langConfig[lang as keyof typeof langConfig];
抽离语言包
locales/editor/zh-cn.ts 中文简体
import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn';
Object.assign(window.CKEDITOR_TRANSLATIONS['zh-cn'].dictionary, {
'Font Size': '字号',
'Font Color': '文字颜色',
'Font Background Color': '背景色',
'Horizontal line': '分割线',
'Remove Format': '清除格式',
'Text alignment': '文本对齐',
'Align left': '左对齐',
'Align right': '右对齐',
'Align center': '居中对齐',
Justify: '两端对齐',
});
locales/editor/zh.ts 中文繁体
import '@ckeditor/ckeditor5-build-classic/build/translations/zh';
Object.assign(window.CKEDITOR_TRANSLATIONS.zh.dictionary, {
'Font Size': '字號',
'Font Color': '文字顔色',
'Font Background Color': '背景色',
'Horizontal line': '分割綫',
'Remove Format': '清除格式',
'Text alignment': '文本對齊',
'Align left': '左對齊',
'Align right': '左對齊',
'Align center': '居中對齊',
Justify: '兩端對齊',
});