问题
从网页上复制图文,粘贴到wangEditor富文本编辑器,图片丢失
分析
粘贴板上的html:
<html>
<body>
<!--StartFragment-->
<meta charset="utf-8">
<b style="font-weight:normal;" id="docs-internal-guid-2711f9db-7fff-e625-709d-953484ba76d9">
<p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Game: </span><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Real Car Crash Compilation</span></p><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Genre: </span><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Simulation</span></p><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Package Link: </span><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://play.google.com/store/apps/details?id=com.crash.car.mgt&hl=en&gl=US</span></p><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Video Link: -</span></p><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Video Time: -</span></p><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Content Description: </span><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Insane Car Crash!</span></p><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Title: </span><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Buckle Up and Get Ready to Crash Your Super Car!</span></p><br /><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Cover</span></p><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;"><span style="border:none;display:inline-block;overflow:hidden;width:602px;height:338px;"><img src="https://lh5.googleusercontent.com/RauJlNS3tO-i3XpTXQ23lLKWul0qdFXiEd_8Q7Fyj6E34UIomZQt_pwv70pYRiKrJOUg_FLy4adRyR2pjTc5hB0n06SLPEmAoy-6kpu1LtgpwZxNUHlmVEJHrYTdsOAOHO2gmFvtE8vnBJs" width="602" height="338" style="margin-left:0px;margin-top:0px;" /></span></span><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;"><span style="border:none;display:inline-block;overflow:hidden;width:602px;height:338px;"><img src="https://lh5.googleusercontent.com/rWz5SrY8vFob9Hb9FbE1ahQUGOwFo3BIOb1gKputxih9nsQ6tT1OgXLw5pkqinMoulQh4YvlZaiaYQdlxpelTtKCeoPpF6sGg6aSd1K8XKCc-ZB9djXFKjkP0NTPh1a_y3HSax6XAShTNxE" width="602" height="338" style="margin-left:0px;margin-top:0px;" /></span></span></p><br /><p dir="ltr" style="line-height:1.2;text-align: justify;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:12pt;font-family:'Times New Roman';color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Highlight</span></p>
</b>
<br class="Apple-interchange-newline">
<!--EndFragment-->
</body>
</html>
wangEditor富文本编辑器渲染后:
如上可知,源html b标签内的文本被全部抽取渲染在span内
寻求帮助
1、github issue
需要调用方自己实现,未查询到有已实现的范例!
作者有对这个问题做说明: 开源编辑器 wangEditor5 发布两个月总结(官网诚接广告,攒钱租服务器)
2、搜索引擎
wangEditor粘贴从word复制的带图片内容的最佳实践
解决从word复制粘贴图片丢失的问题,但原因和我的不一样,这里是因为图片标签和内容分离,只粘贴了标签。最终还是调用的dangerouslyInsertHtml
查看源码
1、监听粘贴事件
2、insertData
粘贴最终调用的还是dangerouslyInsertHtml
3、dangerouslyInsertHtml
源html主要的内容都放在了b标签内,我们跟踪dangerouslyInsertHtml,看b标签会走到哪
b被作为文本标签
4、parseElemHtml
5、 parseTextElemHtml
由上可知,会将b标签内的文本都取出,生成一个text node
从以上分析可知,问题的原因在于,复制的图文是被b标签包裹的,而b被wangEditor视为文本标签,只取其中的文本渲染。
解决
1、重写dangerouslyInsertHtml
wangEditor无法兼容所有的 HTML 格式,这一点官方文档有特别标红说明。也就是说,我们在编辑器输入内容时,wangEdior 会做一些处理(过滤,筛选,转换等)。
作者是因为html格式非常灵活,不可能全部支持!那我们调用方想要支持各种粘贴的场景,实现起来首先得了解wangEditor的dom -> slate node的转换规则和类型,然后扩展。
这需要对wangEditor的实现原理有深入的了解,且听下回分解。
2、 改造源html的dom结构,使其适配wangEditor的转换规则
/**
* 遍历html节点,将TEXT_TAGS节点且包含图片子节点的替换成p
* @param html
*/
const replaceTextNodeToBlock = (html: string) => {
if(!html) {
return '';
}
const div = document.createElement('div');
div.innerHTML = html;
div.setAttribute('hidden', 'true');
document.body.appendChild(div);
const imgs = div.querySelectorAll('img');
if(!imgs || !imgs.length) {
document.body.removeChild(div);
return html;
}
[...imgs].forEach(img => {
let parentN = img.parentNode;
while(parentN && parentN !== div) {
if(TEXT_TAGS.includes(parentN.nodeName.toLowerCase())) {
const p = document.createElement('p');
const { innerHTML, attributes } = (<Element>parentN);
p.innerHTML = innerHTML;
if(attributes && attributes.length) {
console.log('attributes: ', attributes);
// eslint-disable-next-line no-restricted-syntax
for(const attribute of attributes) {
p.setAttribute(attribute.nodeName, attribute.nodeValue as string);
}
}
parentN.parentNode?.replaceChild(p, parentN);
parentN = p.parentNode;
} else {
parentN = parentN.parentNode;
}
}
});
const returnHtml = div.innerHTML;
document.body.removeChild(div);
return returnHtml;
};
/**
* 自定义粘贴。可阻止编辑器的默认粘贴,实现自己的粘贴逻辑。
* @param editor
* @param event
* @returns
*/
export const handleCustomPaste = (editor: IDomEditor, event: ClipboardEvent): boolean => {
console.log('handleCustomPaste: ', editor);
// 获取粘贴的html部分(??没错粘贴word时候,一部分内容就是html),该部分包含了图片img标签
let html = (event.clipboardData as DataTransfer).getData('text/html');
console.log('text/html: ', html);
if(!html) {
return true;
}
// 获取rtf数据(从word、wps复制粘贴时有),复制粘贴过程中图片的数据就保存在rtf中
const rtf = (event.clipboardData as DataTransfer).getData('text/rtf');
console.log('text/rtf: ', rtf);
// 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合
const imgSrcs = findAllImgSrcsFromHtml(html);
// 没有图片
if(!imgSrcs || !Array.isArray(imgSrcs) || !imgSrcs.length) {
return true;
}
if(rtf) { // 该条件分支即表示要自定义word粘贴
// 列表缩进会超出边框,直接过滤掉
html = html.replace(/text-indent:-(.*?)pt/gi, '');
// 从rtf内容中查找图片数据
const rtfImageData = extractImageDataFromRtf(rtf);
// 如果找到
if (rtfImageData.length) {
// TODO:此处可以将图片上传到自己的服务器上
// 执行替换:将html内容中的img标签的src替换成ref中的图片数据,如果上面上传了则为图片路径
html = replaceImagesFileSourceWithInlineRepresentation(html, imgSrcs, rtfImageData)
console.log('result: ', html);
editor.dangerouslyInsertHtml(html);
// 阻止默认的粘贴行为
event.preventDefault();
return false;
}
} else {
const newHtml = replaceTextNodeToBlock(html);
console.log('处理深层次嵌套图片: ', newHtml);
editor.dangerouslyInsertHtml(newHtml);
// 阻止默认的粘贴行为
event.preventDefault();
return false;
}
return true;
};
引申其他问题 - office复制的图片还是会丢失,wps不会
对比两者复制出的html内容
左侧为office,右侧为wps
经调试发现问题出在没识别出office的html中包含img标签,原来是正则的问题,office的html会包含换行
于是将源html去掉换行再进行正则匹配:
/**
* 从html代码中匹配返回图片标签img的属性src的值的集合
* @param htmlData
* @return Array
*/
const findAllImgSrcsFromHtml = (htmlData: string) => {
const imgReg = /<img.*?(?:>|\/>)/gi; // 匹配图片中的img标签
const arr = htmlData.replace(/[\r\n]/g, ' ').match(imgReg); // 筛选出所有的img
if (!arr || (Array.isArray(arr) && !arr.length)) {
return false;
}
const srcArr = [];
const srcReg = /src=['"]?([^'"]*)['"]?/i; // 匹配图片中的src
for (let i = 0; i < arr.length; i++) {
const src = arr[i].match(srcReg);
// 获取图片地址
src && src.length && srcArr.push(src[1]);
}
return srcArr;
}
但是,图片还是没出现!原因是替换src后的图片还是嵌套在文本标签内,需要做replaceTextNodeToBlock处理:
/**
* 自定义粘贴。可阻止编辑器的默认粘贴,实现自己的粘贴逻辑。
* @param editor
* @param event
* @returns
*/
export const handleCustomPaste = (editor: IDomEditor, event: ClipboardEvent): boolean => {
console.log('handleCustomPaste: ', editor);
// 获取粘贴的html部分(??没错粘贴word时候,一部分内容就是html),该部分包含了图片img标签
let html = (event.clipboardData as DataTransfer).getData('text/html');
console.log('text/html: ', html);
if(!html) {
return true;
}
// 获取rtf数据(从word、wps复制粘贴时有),复制粘贴过程中图片的数据就保存在rtf中
const rtf = (event.clipboardData as DataTransfer).getData('text/rtf');
console.log('text/rtf: ', rtf);
// 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合
const imgSrcs = findAllImgSrcsFromHtml(html);
// 没有图片
if(!imgSrcs || !Array.isArray(imgSrcs) || !imgSrcs.length) {
return true;
}
if(rtf) { // 该条件分支即表示要自定义word粘贴
// 列表缩进会超出边框,直接过滤掉
html = html.replace(/text-indent:-(.*?)pt/gi, '');
// 从rtf内容中查找图片数据
const rtfImageData = extractImageDataFromRtf(rtf);
// 如果找到
if (rtfImageData.length) {
// TODO:此处可以将图片上传到自己的服务器上
// 执行替换:将html内容中的img标签的src替换成ref中的图片数据,如果上面上传了则为图片路径
html = replaceImagesFileSourceWithInlineRepresentation(html, imgSrcs, rtfImageData)
console.log('result: ', html);
editor.dangerouslyInsertHtml(html);
// 阻止默认的粘贴行为
event.preventDefault();
return false;
}
} else {
const newHtml = replaceTextNodeToBlock(html);
console.log('处理深层次嵌套图片: ', newHtml);
editor.dangerouslyInsertHtml(newHtml);
// 阻止默认的粘贴行为
event.preventDefault();
return false;
}
return true;
};