如何拷贝 Word 内容到网页编辑器并且保留图片
遇到一个问题,就是有个富文本编辑器需要能从 MS Word 那边复制,粘贴内容到编辑器里面,图片要附带过去。
我在网络上测试了几个编辑器,大都不支持,如果全部不支持,我就可以得出结论,说技术上做不到。不过测试之后发现 CKEditor 和 tinymce 是支持直接粘贴 Word 文档内容,并且能读取到图片。
后面就是各种收集资料和调试,实现流程并不难,第一步现实监听粘贴事件,首先创建一个编辑器:
<div id="editor" contenteditable="true"></div>
<script>
const editor = document.querySelector("#editor");
editor.addEventListener("paste", e => {
const {clipboardData} = e;
console.log(clipboardData.types, clipboardData.items);
});
</script>
监听 paste
事件,触发的时候是 ClipboardEvent
对象,里面包含了 clipboardData
是剪贴板内的数据。
- clipboardData.types 包含了剪贴板里面项目的数据类型,可能是文本或是文件。
- clipboardData.items 不同类型的数据。
- clipboardData.getData() 获取指定类型的数据
简单说,如果从网页拷贝一段内容粘贴到编辑器里面,那么 types
会包含两个数据 ["text/html","text/plain"]
,一个是网页源代码,一个是去除标签后的数据。在需要粘贴的时候,目标软件会依据这个类型,寻找出最合适的那个类型数据导入进去。如果你从浏览器地址栏拷贝地址粘贴进去,这时候类型是["text/plain"]
,只有纯文本数据,没有其他的样式。在系统资源管理器里面,复制一个文件到编辑器粘贴,剪贴板的数据类型就是["Files"]
,因此网页是可以读取剪贴板里面的文件的。
那么如果是从 Word 里面拷贝一段内容,粘贴到网页里面的上面的编辑框内,获取到的剪贴板数据类型是:["text/plain","text/html","text/rtf"]
,编辑器不会识别text/rtf
格式,因此会采用text/html
里面的数据,包含的内容是 Word 导出来的完整网页代码,但是图片路径存在错误:
<img src="file:///..." name="image1.png" align="bottom" width="225" height="225" border="0"/>
因此,你会看到除了图片的未知空着之外,其他格式都是正常的。打印剪贴板内容代码如下:
<script>
const editor = document.querySelector("#editor");
editor.addEventListener("paste", e => {
const {clipboardData} = e;
console.log(clipboardData.types, clipboardData.items);
clipboardData.types.includes("text/html")
&& console.log(clipboardData.getData("text/html"));
});
</script>
text/rtf
是微软的专有格式,里面包含了图片的数据,而不是路径信息,因此解析这个内容,然后转换成 text/html
格式或是直接获取到数据插入富文本编辑器,就可以实现从 Word 拷贝图片的功能。
目前的问题,找不到好的 RTF 格式内容解析的库,如果需要对 Word 拷贝内容有比较高的兼容需求,可以考虑自己编写这个解析库。第三方的有:rtf.js
完整的网页代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/WMFJS.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/EMFJS.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/RTFJS.bundle.min.js"></script>
<style>
html, body {
padding: 0;
margin: 0;
}
body {
background-color: #eee;
}
#editor {
background-color: #fff;
width: 500px;
height: 200px;
margin: 100px auto;
border-radius: 5px;
padding: 15px;
font-size: 18px;
border: red solid 1px;
overflow-y: auto;
}
</style>
</head>
<body>
<div id="editor" contenteditable="true"></div>
<script>
function stringToArrayBuffer(string) {
const buffer = new ArrayBuffer(string.length);
const bufferView = new Uint8Array(buffer);
for (let i = 0; i < string.length; i++) {
bufferView[i] = string.charCodeAt(i);
}
return buffer;
}
const editor = document.querySelector("#editor");
editor.addEventListener("paste", e => {
const {clipboardData} = e;
console.log(clipboardData.types, clipboardData.items);
if (!clipboardData.types.includes("text/rtf")) return;
const rtf = clipboardData.getData("text/rtf");
const doc = new RTFJS.Document(stringToArrayBuffer(rtf));
const meta = doc.metadata();
doc.render().then(function (htmlElements) {
console.log("Meta:");
console.log(meta);
console.log("Html:");
console.log(htmlElements);
htmlElements.forEach(ele => editor.appendChild(ele));
}).catch(error => console.error(error))
e.stopPropagation();
e.preventDefault();
});
</script>
</body>
</html>