这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

我们公司目前在做基于tiptap的在线协同文档,最近需要做导出 pdf、word 需求。

导出 word 文档使用的是html-docx-js-typescript,是用 typescript 重写了一下html-docx-js,可以看到最近的提交记录是 2016 年,貌似已经不维护了,很多 Issues 没人管。

实在找不到其他的 html 转 word 的插件,最后只能使用它来处理,我把我在使用过程中遇到的问题一一列出来,就有了这篇避坑指南。

使用说明

  • 安装

    安装html-docx-js-typescript,同时安装FileSaver用于浏览器端保存文件。

npm install html-docx-js-typescript file-saver --save-dev
npm install @types/html-docx-js @types/file-saver --dev

使用过程遇到的问题及处理方案

字体加粗不生效、字体背景颜色不生效处理

字体加粗<strong>和标记文本元素<mark>标签需要替换为<b><span>标签

const innerHtml = cloneEle.innerHTML
// strong在word中不生效问题
.replace(/<strong>/g, '<b>')
.replace(/<\/strong>/g, '</b>')
// 背景色不生效问题
.replace(/<mark/g, '<span')
.replace(/<\/mark>/g, '</span>')

h1 - h6 标题高度优化及未同步 word 文档标题

我们文档中的标题对应的 HTML 内容长这样

需要将内容转换为类似<h1>xxx</h1>这样,不然 word 中编辑时不能对应标题,修改如下:

// 标题高度和字体失效 需要设置lineHeight和fontWeight
const handleLevelStyle = (cloneEle: HTMLElement) => {
Array.from({ length: 6 }).forEach((_, index) =>
(cloneEle.querySelectorAll(`h${index + 1}`) as unknown as HTMLElement[]).forEach((h) => {
h.innerText = (h.children[0] as HTMLElement).innerText
h.style.fontSize = ''
})
)
}

图片下多出一个白框

Prosemiror-images上传图片后,会在图片后面生成.ProseMirror-separator这个标签,我们在导出时只需要删除它即可。

const removeWhiteBox = (cloneEle: HTMLElement) => {
const separators: NodeListOf<Element> = cloneEle.querySelectorAll(
'.ProseMirror-separator'
)
separators.forEach((separator) =>
separator.parentElement?.removeChild(separator)
)
}

列表 ul、ol

在开始处理之前,先介绍一个插入 DOM 的 API insertAdjacentElement

在 vue、react 这些框架的盛行,基本上我们已经不会再用到 DOM 操作,不过可以了解一下,万一以后用得到呢。

// 将给定元素element插入到调用的元素的某个位置
element.insertAdjacentElement(position, element)

参数position可以是以下位置

  • 'beforebegin': 插入元素之前,类似 insertBefore
  • 'afterbegin': 插入元素第一个 children 之前,类似 prepend
  • 'beforeend': 插入元素最后一个 children 之后,类似 appendChild
  • 'afterend': 插入元素之后,类似 insertAfter

接着我们看一下列表这部分的修改,由于我们项目功能上的需求,列表是使用 div 标签来改造的,所以需要将 div 标签转为 ul/ol,下面是我的实现

const changeDiv2Ul = (div: HTMLElement | Element, parent?: HTMLElement | Element) => {
const kind = div.getAttribute('data-list-kind')
const ul = kind === 'ordered' ? document.createElement('ol') : document.createElement('ul')
const li = document.createElement('li')
// 去除margin 不然在word中会偏移
!parent && (ul.style.margin = '0')
li.innerHTML = div.innerHTML
ul.appendChild(li)
parent ? parent.insertAdjacentElement('afterend', ul) : div.insertAdjacentElement('afterend', ul)
div.parentElement?.removeChild(div) li.querySelectorAll('.list-marker').forEach((marker) => marker.parentElement?.removeChild(marker)) // 内容区域
li.querySelectorAll('.list-content').forEach((content) => {
const span = document.createElement('span')
span.innerHTML = (content.firstChild as HTMLElement).innerHTML
content.insertAdjacentElement('beforebegin', span)
if (content.querySelectorAll('.prosemirror-flat-list').length) {
content.querySelectorAll('.prosemirror-flat-list').forEach((div) => changeDiv2Ul(div, content))
}
content.parentElement?.removeChild(content)
})
}
cloneEle.querySelectorAll('.prosemirror-flat-list').forEach((div) => changeDiv2Ul(div))

复选框 checkbox

复选框 checkbox 的处理,首先考虑的是转为<input type='checkbox' />来处理,结果转完后并没有显示复选框;

接着又想着用 span 标签生成一个方框,<span style='width: 16px;height: 16px...' />,这样总能显示了吧!结果依然不行。

正当我想不到办法的时候,突然灵机一动,可不可以把 word 转成 html 后看看 checkbox 最终会显示成啥样呢?

于是通过在线 word 转 html将 word 转为 html 后,看到复选框对应的 html 内容为<span style="color:#333333; font-family:'Wingdings 2'; font-size:11pt"></span>,改一下吧。

const span = document.createElement('span')
span.innerHTML = `<span style="color:#333333; font-family:'Wingdings 2'; font-size:11pt"></span>`
marker.insertAdjacentElement('beforebegin', span)
marker.parentElement?.removeChild(marker)

转成 word 后,复选框的选中和取消功能也能正常使用。

附件导出、多维表等 iframe 内容

参考了一下钉钉文档

这样就很好改了,只需要把附件对应的节点内容,改为链接即可。

cloneEle.querySelectorAll('.attachment-node-wrap').forEach((attach) => {
const title = `请至One文档查看附件《${attach.getAttribute('name')}》`
const anchorId = attach.parentElement?.getAttribute('data-id')
const a = document.createElement('a')
a.target = '_blank'
a.href = `${location.href}&anchor=${anchorId}`
a.innerHTML = `<span>${title}</span>` attach.insertAdjacentElement('beforebegin', a)
attach.parentElement?.removeChild(attach)
})

未解决的部分

  • 表情无法导出,这个我看了下其他在线协作文档,也有同样的问题。

小结

其实,处理这些问题的方式也是很简单,因为html-docs-js是用html字符串来作为导出文档的输入。如果导出后发现样式不对的情况时,我们只需要去修改html内容即可。

如果有遇到像复选框checkbox这类不知道怎么解决的问题,也可以采用反推,先通过word转html,然后看转为html后的内容,再去修改需要导出的html内容,这也不失为一种解决问题的方式。

以上是我在使用html-docs-js插件时遇到的一些问题及处理方式,如果有遇到同样问题的小伙伴,可以说下你们的处理方式。或者这里没有提到的问题,也欢迎大家补充。

本文转载于:

https://juejin.cn/post/7220244579671916604

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录-html-docs-js避坑指南的更多相关文章

  1. js避坑历险记

    代码改变世界,世界改变码农,码农改变代码! 我就是我,我就是一个码农的武林. 前方JS巨坑出没,请注意集中力! 巨坑1:js精度问题 前段时间去一家物流公司面试,做了一个js题,印象尤为深刻: var ...

  2. Harmony OS 开发避坑指南——源码下载和编译

    Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...

  3. 今天 1024,为了不 996,Lombok 用起来以及避坑指南

    Lombok简介.使用.工作原理.优缺点 Lombok 项目是一个 Java 库,它会自动插入编辑器和构建工具中,Lombok 提供了一组有用的注解,用来消除 Java 类中的大量样板代码. 目录 L ...

  4. electron 编译 sqlite3避坑指南---尾部链接有已经编译成功的sqlite3

    electron 编译 sqlite3避坑指南(尾部链接有已经编译成功的sqlite3) sqlite很好用,不需要安装,使用electron开发桌面程序,sqlite自然是存储数据的不二之选,奈何编 ...

  5. CEF避坑指南(一)——下载并编译第一个示例

    CEF即Chromium Embedded Framework,Chrome浏览器嵌入式框架.它提供了接口供程序员们把Chrome放到自己的程序中.许多大型公司,如网易.腾讯都开始使用CEF进行前端开 ...

  6. Canal v1.1.4版本避坑指南

    前提 在忍耐了很久之后,忍不住爆发了,在掘金发了条沸点(下班时发的): 这是一个令人悲伤的故事,这条情感爆发的沸点好像被屏蔽了,另外小水渠(Canal意为水道.管道)上线一段时间,不出坑的时候风平浪静 ...

  7. Linux下Python3.6的安装及避坑指南

    Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装Python3之前,需要将这些依赖库先行安装好. yum -y install zlib-dev ...

  8. Hive改表结构的两个坑|避坑指南

    Hive在大数据中可能是数据工程师使用的最多的组件,常见的数据仓库一般都是基于Hive搭建的,在使用Hive时候,遇到了两个奇怪的现象,今天给大家聊一下,以后遇到此类问题知道如何避坑! 坑一:改变字段 ...

  9. Android连接远程数据库的避坑指南

    Android连接远程数据库的避坑指南 今天用Android Studio连接数据库时候,写了个测试连接的按钮,然后连接的时候报错了,报错信息: 2021-09-07 22:45:20.433 705 ...

  10. .NET AsyncLocal 避坑指南

    目录 AsyncLocal 用法简介 AsyncLocal 实现原理 AsyncLocal 的坑 AsyncLocal 的避坑指南 HttpContextAccessor 的实现原理 AsyncLoc ...

随机推荐

  1. 48.DRF版本控制

    版本控制 版本控制是前后端分离开发一个非常重要的内容,比如说我们重要服务修改.升级等发生版本变化v1.v2.v3等,但是版本发生了变化比如 v1升级到了v2版本,v1版本还有业务在继续使用,相当于同时 ...

  2. C语言,变长数组的用法

    在我的<C语言,结构体成员的地址>文章中,定义了一个demo_node结构体,其中用到变长数组char addr[0].本文以此为例,对C语言变长数组的基本用法展开介绍. typedef ...

  3. pikachu SQL-inject insert/update注入

    insert 注入 (修改信息处是update注入,和此处同理) 注册页面,用户处输入 1' 发现报错信息 You have an error in your SQL syntax; check th ...

  4. Mqtt开发笔记:Mqtt服务器搭建

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  5. itertools.chain.from_iterable()将嵌套列表合并成一个

    from itertools import chain a = [[1,2],[3,4]] print(chain.from_iterable(a)) # [1,2,3,4]

  6. 深入理解String

    深入理解String String是Java中的一个类,是一个引用类型,用于表示字符串.它是不可变的(immutable),即一旦创建,其值就不能被修改.任何对String对象的修改操作都会创建一个新 ...

  7. 教程|在矩池云使用 Stable Diffusion web UI v1.5 模型和 ControlNet 插件

    今天给大家介绍下如何在矩池云使用 Stable Diffusion web UI v1.5 模型和 Stable Diffusion ControlNet 插件. 租用机器 租用机器需要选择内存大于8 ...

  8. 全面掌握Directory.Build.props

    为什么需要集中管理版本号? 1)同一个产品包含多个模块,对同一个包指定了不同的版本 比如A模块用了"Serilog.Sinks.Async"的"1.1.2"版本 ...

  9. 【Azure Redis 缓存】遇见Azure Redis不能创建成功的问题:至少一个资源部署操作失败,因为 Microsoft.Cache 资源提供程序未注册。

    问题描述 在中国区微软云上创建Redis失败.收到的错误消息为: { "code": "DeploymentFailed", "message&quo ...

  10. [Python] 子线程退出孙线程不退出

    遇到了一个大坑! 如图,在子线程ThreadFunc退出之后,ThreadFunc2依旧在运行... 根本不会结束 但是官方文档中说明了,只要设置了daemon不为None 就能设置子线程是守护线程, ...