ONLYOFFICE 协作空间 企业版使用秘籍-4.创建插件的完整教程

2025-06-15 18:48:29 0点赞 0收藏 0评论
ONLYOFFICE 协作空间 企业版使用秘籍-4.创建插件的完整教程

在当今这个注重效率与协作的时代,生产力工具也需不断进化。协作空间插件为用户提供了更现代化的文档管理与交互方式。在本文中,我们将以官方插件示例为基础,带你一步步创建属于自己的协作空间插件。

关于 ONLYOFFICE 协作空间插件 SDK

    ONLYOFFICE 协作空间插件 SDK 是一个基于 TypeScript 的 npm 包,提供丰富的接口,帮助您开发自定义插件并集成到协作空间门户中。插件的开发生命周期包括规划、开发、测试和使用。

    为帮助您更好地理解使用方式,我们提供了一个包含示例插件的代码库。接下来,我们将以这些示例为基础,详细介绍插件开发的完整流程。

规划

明确插件的用途,思考其使用场景以及需要集成的第三方服务。

步骤 1:安装所有必要的软件包和程序

  • @onlyoffice/docspace-plugin-sdk npm 软件包

要全局安装 @onlyoffice/docspace-plugin-sdk npm 软件包,请使用以下命令:

npmi-g@onlyoffice/docspace-plugin-sdk


步骤 2:设计插件的工作方式

选择允许您向协作空间添加必要功能的服务。

例如,在我们的插件示例中,我们使用:

  • AssemblyAI 将音频和视频文件中的语音转换为文本;

  • ConvertAPI 将文档、电子表格、演示文稿和表单转换为 PDF;

  • Draw.io 用于创建、编辑和插入专业图表。

    注意!请确保服务文档可用,并检查其许可证和API方法的可用性。对于某些服务,用户需获取 API 密钥后才能使用插件。

考虑插件的部署位置、整体结构以及用户如何与其组件交互。根据这些需求,明确所需的插件类型和项目。如需了解更多,请参阅插件 SDK 文档中的 Plugin types 和 Plugin items 部分。

    例如,对于语音转文本插件,我们使用以下插件接口:

  • IPlugin:每个插件必需的基础接口,包含用于将插件嵌入协作空间的 PluginStatus 变量。

  • ApiPlugin:在集成第三方服务时使用。

  • ISettingsPlugin 和 ISettings :用于添加插件的配置界面,用户可通过“设置 > 集成 > 插件”访问并调整相关参数。

  • IContextMenuPlugin 和 IContextMenuItem:用于实现上下文菜单操作。例如,在语音转文本插件中,可对音视频文件执行“转换为文本”的操作。

    接口列表可能更为丰富。以 draw.io 插件为例:

  • IMainButtonPlugin 和 IMainButtonItem:用于实现主按钮操作。在 draw.io 插件中,用户可通过“我的文档”页面或选定文件夹中的“操作按钮 > 更多”菜单,创建 draw.io 图表。

  • IFilePlugin 和 IFileItem:用于与特定文件类型交互。在此示例中,即用于处理 .drawio 文件的集成与操作。

 确定插件的结构,此处介绍了所有必需的文件:https://api.onlyoffice.com/zh-CN/docspace/plugins-sdk/usage-sdk/plugin-structure/

    其余内容可根据个人偏好组织。例如,在 draw.io 插件中,每种插件类型的代码被放置在独立的文件夹内;而在语音转文本插件中,相关代码则存放于 src 文件夹中。

图片图片


为您的插件选择一个名称并撰写描述。

开发

现在所有准备工作都已完成,您可以开始开发插件了。

步骤 1:创建插件模板

创建插件模板并配置相关设置,这些设置将在协作空间插件配置界面中显示。

要生成模板,只需在终端中运行以下 npx 命令:

npx create-docspace-plugin

如果 npx 命令不可用,请使用以下命令全局安装 @onlyoffice/docspace-plugin-sdk npm 包:

npm i -g @onlyoffice/docspace-plugin-sdk

通过在对话框中指定设置,在终端中配置插件。此处介绍了所有设置:https://api.onlyoffice.com/zh-CN/docspace/plugins-sdk/usage-sdk/creating-plugin-template/

对于语音转文本插件,您可以使用以下值:

图片图片

    您可以稍后在 package.json file 中更改所有指定的参数,还可以在任何项目中创建插件,只需将 @onlyoffice/docspace-plugin-sdk npm 包添加为依赖项,并在 package.json 文件中指定所有必要的字段即可。

步骤 2. 配置插件入口点

    在模板创建步骤中,插件入口点文件 index.ts 会在 src 文件夹中自动创建。此文件是必需的。此文件包含您在上一步中选择的插件类型的所有基本方法。您可以随时更改此文件。

如果您不使用模板自行创建插件作为插件入口点,可以使用我们现成的插件示例中的代码。它将完美运行。

步骤 3:添加插件图标

    在插件根目录下创建 assets 文件夹,并将所有需要用到的图标文件放入其中。图标的数量和尺寸取决于您所开发的插件类型。

以语音转文本插件为例,我们需要以下图标:

  • 默认插件类型需要一个 logo 图片,对应 package.json 文件中的 logo 字段。该徽标将显示在协作空间插件设置中。建议尺寸为 48×48 像素,若尺寸不符,系统会自动缩放至该大小。

(no title)(no title)
  • 上下文菜单插件在转换为文本按钮上使用图标。所需的图标尺寸为 16×16 像素。否则,它将被压缩到此尺寸。

此图标也可用作主按钮图标。例如,在 draw.io 插件中,上下文菜单和主按钮菜单使用相同的图标。

图片图片

draw.io 插件还使用 .drawio 文件附近的特定文件图标,这些文件是通过文件插件类型创建的。表格格式的首选图标大小为 32×32 像素:

图片图片


步骤 4. 配置插件的界面元素

例如,draw.io 插件包含两个主要的 UI 元素——模态窗口和图表编辑器。创建用于配置每个元素的文件。为了方便起见,您可以将这些文件放在单独的 DrawIO 文件夹中。

在 Dialog.ts 文件中,配置模态窗口设置。指定用于将 draw.io 网站嵌入模态窗口的 IFrame UI 组件:

export const frameProps: IFrame = {    width: "100%",    height: "100%",    name: "test-drawio",    src: "",  }

创建 IBox 容器以将 iframe 添加到其中:

const body: IBox = {    widthProp: "100%",    heightProp: "100%",      children: [      {        component: Components.iFrame,        props: frameProps,      },    ],  }

配置模态窗口属性:

export const drawIoModalDialogProps: IModalDialog = {    dialogHeader: "",    dialogBody: body,    displayType: ModalDisplayType.modal,    fullScreen: true,    onClose: () => {      const message: IMessage = {        actions: [Actions.closeModal],      }      return message    },    onLoad: async () => {      return {        newDialogHeader: drawIoModalDialogProps.dialogHeader || "",        newDialogBody: drawIoModalDialogProps.dialogBody,      }    },    autoMaxHeight: true,    autoMaxWidth: true,

在 Editor.ts 文件中,配置图表编辑器。使用以下参数创建 DiagramEditor 函数:

ONLYOFFICE 协作空间 企业版使用秘籍-4.创建插件的完整教程ONLYOFFICE 协作空间 企业版使用秘籍-4.创建插件的完整教程

*DiagramEditor* 的完整代码可在此处找到:https://github.com/ONLYOFFICE/docspace-plugins/blob/master/draw.io/src/DrawIO/editor.ts

步骤 5. 创建插件类型

默认插件已准备就绪,您可以开始实现其他插件类型的代码。

每种插件类型都有对应的插件项。以上下文菜单为例,您需要定义在音频或视频文件上右键点击时显示的菜单项。

export const contextMenuItem: IContextMenuItem = {  key: "speech-to-text-context-menu-item",  label: "Convert to text",  icon: "speech-to-text-16.png",  onClick: assemblyAI.speechToText,  fileType: [FilesType.video],  withActiveItem: true,}图片图片

    您可以添加更多插件类型。例如,draw.io 插件可以从主按钮菜单访问,因此我们需要指定主按钮项。

const mainButtonItem: IMainButtonItem = {  key: "draw-io-main-button-item",  label: "Draw.io",  icon: "drawio.png",  onClick: (id: number) => {    drawIo.setCurrentFolderId(id)      const message: IMessage = {      actions: [Actions.showCreateDialogModal],      createDialogProps: {        title: "Create diagram",        startValue: "New diagram",        visible: true,        isCreateDialog: true,        extension: ".drawio",        onSave: async (e: any, value: string) => {          await drawIo.createNewFile(value)        },        onCancel: (e: any) => {          drawIo.setCurrentFolderId(null)        },        onClose: (e: any) => {          drawIo.setCurrentFolderId(null)        },      },    }    return message  },  // items: [createItem],}

    单击主按钮项时,将出现模式窗口,您可以在其中输入图表的名称并打开一个空的 .drawio 文件。

图片图片

    对于 draw.io 插件,您还需要配置文件插件类型,该类型在用户打开特定的 .drawio 文件时起作用:

定义表示为带有特定扩展名(.drawio)和图标的文件项:

 export const drawIoItem: IFileItem = {     extension: ".drawio",     fileTypeName: "Diagram",     fileRowIcon: "drawio-32.svg",     fileTileIcon: "drawio-32.svg",     devices: [Devices.desktop],     onClick,   }

    定义 onClick 事件,该事件将在每次打开 .drawio 文件时执行 editDiagram 方法:

  const onClick = async (item: File) => {     return await drawIo.editDiagram(item.id)   }(no title)(no title)


步骤 6:创建设置插件类型

配置设置插件类型,以便为用户提供管理员设置。

创建一个用于存放插件设置的容器:

const descriptionText: TextGroup = {     component: Components.text,     props: {       text: "To generate API token visit https://www.assemblyai.com",       color: "#A3A9AE",       fontSize: "12px",       fontWeight: 400,       lineHeight: "16px",     },   }      const descGroup: BoxGroup = {     component: Components.box,     props: {children: [descriptionText]},   }      const parentBox: IBox = {     displayProp: "flex",     flexDirection: "column",     // marginProp: "16px 0 0 0",     children: [tokenGroup, descGroup],   }

在设置描述中,指明需要生成 API 令牌才能使用该插件。

使用 ISettings 对象配置管理员设置:

const adminSettings: ISettings = {     settings: parentBox,     saveButton: userButtonComponent,     onLoad: async () => {       assemblyAI.fetchAPIToken()          tokenInput.value = assemblyAI.apiToken          if (!assemblyAI.apiToken) {         return {           settings: parentBox,         }       }         plugin.setAdminPluginSettings(adminSettings)          return {settings: parentBox}     }

指定 onLoad 事件,该事件定义加载设置块时将显示哪些插件设置。

每个设置项都在单独的文件(按钮、令牌)中确定。

图片图片

步骤 7:创建主插件代码文件

在 src 文件夹中创建一个包含主插件代码的文件。此文件是必需的。请参考第三方服务的文档来编写此代码。

让我们看看 AssemblyAI.ts 文件的详细结构:

定义 AssemblyAI 类,并包含所有必要的变量和方法:

  • 变量及其说明:

apiURL

定义 API URL。

apiURL = ""

currentFileId

定义当前文件 ID。

const currentFileId: numbernull | number = null

apiToken

定义 API 令牌。

 apiToken = ""

  • 方法及其描述

createAPIUrl

创建 API URL。

createAPIUrl = () => {     const api = plugin.getAPI()       this.apiURL = api.origin.replace(//+$/, "")       const params = [api.proxy, api.prefix]       if (this.apiURL) {       for (const part of params) {         if (!part) {           continue         }         const newPart = part.trim().replace(/^/+/, "")         if (newPart) {           if (this.apiURL.length !== 0 && this.apiURL[this.apiURL.length - 1] === "/") {             this.apiURL += newPart           } else {             this.apiURL += `/${newPart}`           }         }       }     }   }

setAPIUrl

设置 API URL。

 setAPIUrl = (url: string) => {     this.apiURL = url   }

getAPIUrl

返回 API URL。

 getAPIUrl = () => {     return this.apiURL   }

setAPIToken

设置 API 令牌。

   setAPIToken = (apiToken: string) => {     this.apiToken = apiToken   }

getAPIToken

返回 API 令牌。

   getAPIToken = () => {     return this.apiToken   }

fetchAPIToken

获取 API 令牌。

   fetchAPIToken = async () => {     const apiToken = localStorage.getItem("speech-to-text-api-token")       if (!apiToken) {       return     }       this.setAPIToken(apiToken)     plugin.updateStatus(PluginStatus.active)   }

saveAPIToken

保存 API 令牌。

   saveAPIToken = (apiToken: string) => {     localStorage.setItem("speech-to-text-api-token", apiToken)       let status     if (apiToken) {       status = PluginStatus.active     } else {       status = PluginStatus.hide     }     plugin.updateStatus(status)   }

saveAPIToken

保存 API 令牌

 setCurrentFileId = (id: number | null) => {     this.currentFileId = id   }

uploadFile

上传将被转录的文件。

uploadFile = async (apiToken: string, path: string, data: Blob) => {     console.log(`Uploading file: ${path}`)       const url = "https://api.assemblyai.com/v2/upload"       try {       const response = await fetch(url, {         method: "POST",         body: data,         headers: {           "Content-Type": "application/octet-stream",           "Authorization": apiToken,         },       })         if (response.status === 200) {         const responseData = await response.json()         return responseData["upload_url"]       }       console.error(`Error: ${response.status} - ${response.statusText}`)       return null     } catch (error) {       console.error(`Error: ${error}`)       return null     }   

}

transcribeAudio

转录音频文件。

transcribeAudio = async (apiToken: string, audioUrl: string) => {     console.log("Transcribing audio... This might take a moment.")       const headers = {       "authorization": apiToken,       "content-type": "application/json",     }     const response = await fetch("https://api.assemblyai.com/v2/transcript", {       method: "POST",       body: JSON.stringify({audioUrl}),       headers,     })       const responseData = await response.json()     const transcriptId = responseData.id       const pollingEndpoint = `https://api.assemblyai.com/v2/transcript/${transcriptId}`       while (true) {       const pollingResponse = await fetch(pollingEndpoint, {headers})       const transcriptionResult = await pollingResponse.json()         if (transcriptionResult.status === "completed") {         return transcriptionResult       } else if (transcriptionResult.status === "error") {         throw new Error(`Transcription failed: ${transcriptionResult.error}`)       } else {         await new Promise((resolve) => {           setTimeout(resolve, 3000)         })       }     }   }

speechToText

实现插件功能。

speechToText = async (id: number) => {     if (!this.apiToken) {       return     }     this.setCurrentFileId(null)     if (!this.apiURL) {       this.createAPIUrl()     }     const response = await fetch(`${this.apiURL}/files/file/${id}`)     const data = await response.json()     const {viewUrl, title, folderId, fileExst} = data.response     const file = await fetch(viewUrl)     const fileBlob = await file.blob()     const uploadUrl = await this.uploadFile(this.apiToken, viewUrl, fileBlob)     const transcript = await this.transcribeAudio(this.apiToken, uploadUrl)     const blob = new Blob([transcript.text], {       type: "text/plain;charset=UTF-8",     })     const newFile = new File([blob], "blob", {       type: "",       lastModified: Date.now(),     })     const formData = new FormData()     formData.append("file", newFile)     const newTitle = `${title.replaceAll(fileExst, "")} text`     try {       const sessionRes = await fetch(         `${this.apiURL}/files/${folderId}/upload/create_session`,         {           method: "POST",           headers: {             "Content-Type": "application/json;charset=utf-8",           },           body: JSON.stringify({             createOn: new Date(),             fileName: `${newTitle}.txt`,             fileSize: newFile.size,             relativePath: "",           }),         },       )       const response = await sessionRes.json()       const sessionData = response.response.data       const data = await fetch(`${sessionData.location}`, {         method: "POST",         body: formData,       })       const jsonData = await data.json()       const {id: fileId} = jsonData.data       return fileId     } catch (e) {       console.log(e)     }     return {       actions: [Actions.showToast],       toastProps: [{type: ToastType.success, title: ""}],     } as IMessage   }

声明 AssemblyAI 类实例:

const assemblyAI = new AssemblyAI()

导出创建的插件实例:

export default assemblyAI

测试

要检查插件的功能并修复任何潜在错误,请测试插件:

  • 按照此处的说明构建准备好的插件:https://api.onlyoffice.com/docspace/plugins-sdk/usage-sdk/building-plugin/

dist 文件夹将在插件根文件夹中创建,插件存档将放置在其中。此存档是完整的插件,可以上传到协作空间门户。

  • 将您的插件上传到协作空间门户,并彻底测试其外观和功能:https://api.onlyoffice.com/docspace/plugins-sdk/usage-sdk/adding-plugin/

请注意!您只能在服务器协作空间版本中上传自己的插件。

    如果出现任何错误,请修复插件的源代码,然后重复构建和测试的过程。

现在您的插件已经过测试并正常运行,您可以将其添加到协作空间服务器版本并开始使用。

    协作空间插件为文档管理和团队协作提供了高效且便捷的解决方案。通过与用户常用平台的集成,它能有效消除协作障碍,提升各类工作流程的效率。

 福利推荐-获取软件的方法

         豆豆容器市场专注提供优质Docker应用服务,集成一键式容器安装功能,助力用户快速部署OnlyOffice、协作空间、Nextcloud、可道云等办公应用。平台新增IPv6内网直连技术,搭配自动化SSL证书配置及智能域名解析功能,为家庭云服务提供完整技术方案,简化私有云搭建与运维流程,轻松实现高效云端协作管理。

        onlyoffice已经支持ARM和x86双模式安装,其他软件同步支持中。

        地址: https://ds.sendtokindle.net.cn/

图片图片


展开 收起
0评论

当前文章无评论,是时候发表评论了
提示信息

取消
确认
评论举报

相关文章推荐

更多精彩文章
更多精彩文章
最新文章 热门文章
0
扫一下,分享更方便,购买更轻松