背景介绍
CKEditor5是个比较流行的富文本编辑器,本身已经支持从 Word、Excel 和 Google Docs 粘贴内容,但是测试发现,从WPS粘贴内容时,文字部分全部变成了空格。查了官方的资料以及GitHub上别人的提问得知官方本身是不兼容从WPS复制内容的,但是WPS在国内又比较流行,该问题还是要解决一下。
网上搜了其他相关帖子,有人写的还是比较好的,但是实操性不太强,在此梳理下自己的解决过程,有需要的同学可以参考一下。
解决过程
步骤1:访问官方提供的工具,来自定义你需要的ckeditor的配置,如果要支持从word复制内容,需要选中该组件,如下图所示:
最后配置完,会生成一个压缩包,解压该压缩包,在sample目录下有个index.html文件,打开该文件,可以看到你配置的组件的样子。
可以在这个index页面尝试从word和wps复制内容看看效果。
步骤2:下载CKEditor5的源码,GitHub地址:https://github.com/ckeditor/ckeditor5,选择和步骤1相同的tag分支,在packages目录中找到ckeditor5-paste-from-office,该包即时Paste from Office组件的源码了,我们需要修改相关源码。
步骤3:修改源码,在ckeditor5-paste-from-office/src/filters目录找到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, '><' );
}
这是修改后的代码:
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 => {
const innerTextLength = el.innerText.length || 0;
el.innerText = Array( innerTextLength + 1 ).join( '\u00A0 ' ).substr( 0, innerTextLength );
} );
}
这是修改后的代码:
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.innerText = Array( innerTextLength + 1 ).join( '\u00A0 ' ).substr( 0, innerTextLength );
}
} );
}
在ckeditor5-paste-from-office/src/filters目录找到image.js,做如下修改
这是原码:
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 = [];
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;
}
这是修改的代码:
function extractImageDataFromRtf( rtfData ) {
if ( !rtfData ) {
return [];
}
let regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
let regexPicture = new RegExp( '(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g' );
let 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;
}
步骤4:在步骤1下载的工程中创建自定义目录,将ckeditor5-paste-from-office复制到该目录,如下是我的目录结构:
修改ckeditor5-35.4.0/src/ckeditor.js文件
# 将
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js';
# 改为(看自己定义的是什么目录名,替换即可)
import PasteFromOffice from '../custom/ckeditor5-paste-from-office/src/pastefromoffice.js';
执行npm install安装依赖
执行npm run build打包,会在build目录重新生成结果文件。
执行结束后,重新打开sample目录下的index.html,再试试从wps复制内容吧
步骤5:将ckeditor5-35.4.0引入到工程,我用的是vue2,在工程根目录执行如下命令即可:
yarn add file:.\ckeditor5-35.4.0\