onlyoffice 音转文插件开发(数据是通过websocket发过来的)

501 阅读5分钟

1.png

onlyoffice 音转文插件开发

由于公司最近的业务需求,需要前端传入word模版的时候保留样式,并且需要支持音转文实时插入(一想到这就一头大)

word 导入到网页中保留样式(想过富文本的方法)

docx-preview 这个js库,感觉挺不错, word支持度测试验证:

  1. 文字颜色,正常
  2. 文字背景色,正常
  3. 多种字号大小,正常
  4. 有序列表、无序列表,正常
  5. 下标、上标,正常
  6. word中的图片转换html后,正常(可选base64 和 blob 方式)
  7. 表格,正常
  8. 合并单元格,正常
  9. 带背景色的、代码高亮,正常

docx-preview.js 更好,其他的没他这么好的效果

mammoth.js

对格式支持较差,字号、右对齐、居中等不支持;文字颜色、文字背景色,不支持 给个体验地址体验一下docx-preview.js和mammoth.js word 转html

转换的预览效果

image.png

体验地址

无奈都达不到要求,中间已经要放弃了,因为word->html的转化始终是一个痛点。

上周看到了onlyoffice插件形式开发,决定再试一次👏👏

image.png

onlyoffice插件开发

大致而言 onlyoffice插件开始指南 这上面会告诉你怎么去开发一款自己的插件

开发前置需要的环境配置

在社区看到了程序员未央-# 如何构建基于 Onlyoffice 的私有云 Office 办公体验的这篇文章,docker以及在vue中使用onlyoffice

在这里我们只需要前面的docker 就可以了,后面的就在插件里面编写

一个插件的基本配置文件

. ├── config.json # 插件配置文件 
  ├── icon.png # 插件图标 
  ├── icon@2x.png 
  ├── index.html # 插件入口文件 
  ├── main.js # 插件主程序入口文件 
  └── translations # 国际化配置 
    └── zh-CN.json

这里面是我的index.html文件

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>开始填充</title>
    <script src="scripts/jquery.min.js"></script>
    <script type="text/javascript" src="scripts/plugins.js"></script>
    <script type="text/javascript" src="scripts/plugins-ui.js"></script>
    <link rel="stylesÏheet" href="resources/css/plugins.css">
    <link rel="stylesheet" href="resources/css/plugin_style.css">
    <script type="text/javascript" src="scripts/js-cookie.js"></script>
    <!-- <script>
        function setCurrentCookies() {
            Cookies.set('trailId','170363935175654303000000002',{path:'/'})
            Cookies.set('onlyofficeHost','192.168.0.167',{path:'/'})
            Cookies.set('transliterationList', '[{"label":"首席审理员","value":"01"},{"label":"审理员","value":"02"},{"label":"助理审理员","value":"03"},{"label":"记录员","value":"04"},{"label":"被申请人","value":"05"},{"label":"被申请人代理人","value":"06"},{"label":"申请人","value":"07"},{"label":"申请人代理人","value":"08"},{"label":"证人","value":"09"},{"label":"证人","value":"10"}]')
        }
        setCurrentCookies()
    </script> -->
    <script type="text/javascript" src="scripts/webscoket.js"></script>
    <script type="text/javascript" src="scripts/helloworld.js"></script>

    <!-- <script type="text/javascript" src="scripts/translation.js"></script> -->
</head>

<body id="body"></body>
<div id="translationBox">
    
</div>

</html>

然后我是直接在vscode 上 运行了liveserver插件

音转文部分的代码

;(function (window, undefined) {
	var websocketHandler = ''
	function GetQueryString(key) {
		var url = window.location.href //首先获取url
		if (url.indexOf('?') != -1) {
			//判断是否有参数
			var strSub = null
			var str = url.split('?') //根据?截取url
			var strs = str[1].split('&') //str[1]为获取?号后的字符串,并且根据&符号进行截取,得到新的字符串数组,这个字符串数组中有n个key=value字符串
			var value = ''
			for (var i = 0; i < strs.length; i++) {
				//遍历字符串数组
				strSub = strs[i].split('=') //取第i个key=value字符串,并用“=”截取获得新的字符串数组 这个数组里面第0个字符是key,第1个字符value
				if (strSub[0] == key) {
					//判断第0个字符与该方法的参数key是否相等,如果相等 则返回对应的值value。
					value = strSub[1]
				}
			}
			return value
		}

		return ''
	}
	function returnCurrentSpeaker(speaker) {
		let translationsList = JSON.parse(Cookies.get('transliterationList'))
		let speakerRole = ''
		translationsList.map((item) => {
			if (item.value == speaker) {
				speakerRole = item.label
			}
		})
		return speakerRole
	}
	// window.Asc.plugin.name = '停止转写'
    // console.log(window.Asc.plugin)
	window.Asc.plugin.init = function () {
		localStorage.setItem('instertElement', 0)
		// Cookies.set('instertElement', 0, { path: '/', domain: window.location.hostname })
		websocketHandler = new createWebSocket(function () {
			return `wss://${Cookies.get('onlyofficeHost')}:30070/trailv2/api/voice/msg?trailId=${Cookies.get('trailId')}`
		})
		// console.log(window.Asc.plugin)
		// 设置接收到 WebSocket 消息时的回调函数
		websocketHandler.onMessageCallback = function (data) {
			handleWebSocketMessage(data)
		}
		function handleWebSocketMessage(data) {
			// 根据你的数据结构更新 OnlyOffice 文档
			if (data.msgType === '1002') {
				window.Asc.scope.text = returnCurrentSpeaker(data.speaker) + ':' + data.result
				window.Asc.scope.bookName = data.index.toString()
				window.Asc.scope.isInsert = data.sentenceEndTime
				window.Asc.scope.replaceStringText = '[正在转写中....]'
				//这个是第一个可行的方案
				if (data.result) {
					// 	if (!window.Asc.scope.isInsert) {
					// 		var oProperties = {
					// 			searchString: Asc.scope.replaceStringText,
					// 			replaceString: data.result,
					// 			matchCase: true,
					// 		}
					// 		//execute method for search and replace
					// 		window.Asc.plugin.executeMethod('SearchAndReplace', [oProperties])
					// 	} else {
					// 		window.Asc.plugin.callCommand(function () {
					// 			console.log('this', this)
					// 			// console.log(window.Asc.scope)
					// 			var oDocument = Api.GetDocument()
					// 			var oParagraph = Api.CreateParagraph()
					// 			oParagraph.AddText(Asc.scope.text)
					// 			oDocument.InsertContent([oParagraph])
					// 			// Asc.scope.TextArray.push(Asc.scope.bookName)

					// 			// Asc.scope.replaceStringText = Asc.scope.text
					// 		}, false)
					// 	}

					//现在实验第二种方案
					window.Asc.plugin.callCommand(
						function () {
							var oDocument = Api.GetDocument()
							var oRun = Api.CreateRun()
							var oNormalStyle = oDocument.GetDefaultStyle('paragraph')
							/// 将 index 转换为字符串,确保作为标识符使用
							var uniqueTag = 'Tag_' + Asc.scope.bookName
							// 标识是否找到了相同标识符的书签
							var foundBookmark = false
							var instertElement = 0

							// 遍历文档中的所有元素
							for (let i = 0; i < oDocument.GetElementsCount(); i++) {
								var oElement = oDocument.GetElement(i)

								// 判断元素是否存在并且是段落类型
								if (oElement) {
									var oParagraph = oElement

									// 获取段落的文本内容
									var paragraphText = oParagraph.GetText()

									// 检查文本内容中是否包含标识符
									if (paragraphText.indexOf(Asc.scope.replaceStringText) !== -1) {
										// 删除旧的段落
										oParagraph.RemoveAllElements()

										// 创建新的段落并插入
										// var oNewParagraph = Api.CreateParagraph()
										if (Asc.scope.isInsert === 0) {
											oParagraph.SetStyle(oNormalStyle)
											oParagraph.AddText(Asc.scope.text + '  ')
											oRun.AddText(Asc.scope.replaceStringText)
											oRun.SetColor(255, 111, 61)
											oRun.SetBold(true)
											oRun.SetHighlight('darkRed')
											oParagraph.AddElement(oRun)
										} else {
											oParagraph.SetStyle(oNormalStyle)
											oParagraph.AddText(Asc.scope.text)
										}
										localStorage.setItem('instertElement', i)
										// Cookies.set('instertElement',i,{path:'/'})
										oDocument.InsertContent([oParagraph])

										// 标记找到标识符并执行操作
										foundBookmark = true
										break
									}
								}
							}
							console.log('foundBookmark', foundBookmark)
							// 如果没有找到相同标识符的段落,创建一个新的段落并插入
							if (!foundBookmark) {
								var oParagraph = Api.CreateParagraph()
								oParagraph.SetStyle(oNormalStyle)
								oParagraph.AddText(Asc.scope.text + '  ')
								oRun.AddText(Asc.scope.replaceStringText)
								oRun.SetColor(255, 111, 61)
								oRun.SetBold(true)
								oRun.SetHighlight('darkRed')
								// 插入新段落
								oParagraph.AddElement(oRun)

								if (localStorage.getItem('instertElement') && localStorage.getItem('instertElement') != 0) {
									console.log(localStorage.getItem('instertElement'))
									oDocument.AddElement(Number(localStorage.getItem('instertElement')) + 1, oParagraph)
								} else {
									oDocument.InsertContent([oParagraph])
								}
							}
						},
						false,
						true,
						function () {}
					)
				}
			}
		}
	}
	window.Asc.plugin.button = function (id) {
        console.log(id)
    }
})(window, undefined)

当然在写这一部分代码的时候遇到过几个问题

  1. 插件怎么和当前页面通信?
  2. 怎么保证音转文可以一边插入一边修改?
  3. 怎么保证我不想在当前位置插入时,可以让插件在新的位置插入?
  4. 怎么让插入的那一段不可编辑?

前面的3个问题都已经解决了,但是第4个问题还没有想到好的解决方案,后续解决了有更新的话再来补充吧

插件怎么和当前页面通信?

当时想到的是通过localStorage或者sessionStorage来进行通信,理想很美好,现实很骨感啊!!!!

image.png

可以看到onlyoffice和前端的项目地址 不同源。。。。难搞

后面想到了用Cookies的方式去做,因为cookie在域名相同端口不同的情况下是可以设置path的,我这聪明的大脑一下就有了思路,我直接把path 设置到根域名下不就可以了

第二和第三个问题在代码中已经写了