Cómo desarrollar un plugin para DocSpace: tutorial completo paso a paso
En un mundo donde la velocidad y la colaboración son esenciales, las herramientas de productividad deben estar a la altura. Los plugins de DocSpace ofrecen una forma moderna de mejorar la manera en que los usuarios gestionan e interactúan con los documentos. En esta guía, te llevaremos a través de cómo crear tu propio plugin para DocSpace utilizando ejemplos reales de nuestros ejemplos oficiales de plugins.
Acerca del SDK de plugins de ONLYOFFICE DocSpace
El SDK de plugins de ONLYOFFICE DocSpace es un paquete npm basado en motores TypeScript que proporciona interfaces que permiten crear tus propios plugins e integrarlos en el portal de DocSpace. El ciclo de vida del desarrollo de plugins incluye planificación, desarrollo, pruebas y uso.
Para ilustrar cómo usar nuestro SDK de plugins, hemos creado un repositorio con ejemplos de plugins. Vamos a sumergirnos en todo el proceso de desarrollo de plugins basándonos en nuestros ejemplos de plugins.
Planificación
Define el propósito de tu plugin, piensa en dónde lo vas a acceder y qué servicios de terceros necesitas conectar.
Paso 1. Instala todos los paquetes y programas necesarios
- ONLYOFFICE DocSpace on-premises.
- Paquete npm @onlyoffice/docspace-plugin-sdk
Para instalar el paquete npm @onlyoffice/docspace-plugin-sdk globalmente, usa el siguiente comando:
npm i -g @onlyoffice/docspace-plugin-sdk
Paso 2. Diseña cómo funcionará tu plugin
Elige el servicio que te permita agregar la funcionalidad necesaria a tu DocSpace.
Por ejemplo, en nuestros ejemplos de plugins, usamos:
- AssemblyAI para convertir el habla de archivos de audio y vídeo en texto;
- ConvertAPI para convertir documentos, hojas de cálculo, presentaciones y formularios en PDF;
- Draw.io para crear, editar e insertar diagramas profesionales.
Nota: Asegúrate de que la documentación del servicio esté disponible, revisa su licencia, disponibilidad de métodos de la API, etc. Para algunos servicios, el usuario debe obtener una clave de la API para comenzar a usar el plugin.
Piensa en dónde implementarás el plugin, cuál será la estructura de tu plugin, cómo el usuario interactuará con los componentes del plugin, etc. Haz una lista de los tipos de plugins y elementos necesarios según esta información. Para más información, lee las secciones Tipos de plugins y Elementos de plugins de la documentación del SDK de plugins.
Por ejemplo, para el plugin de conversión de habla a texto, usamos las siguientes interfaces de plugins:
- IPlugin. Requerido para cada plugin. Contiene la variable PluginStatus, utilizada para insertar el plugin en DocSpace.
- ApiPlugin. Requerido, ya que implementamos un servicio de terceros.
- ISettingsPlugin y ISettings. Se usan para agregar el bloque de configuración del plugin. Los usuarios accederán a este bloque desde Ajustes -> Integración -> Plugins para ajustar los parámetros del plugin.
- IContextMenuPlugin y IContextMenuItem. Se usan para implementar una acción en el menú contextual. En el plugin de conversión de habla a texto, estará disponible para archivos de vídeo y audio, permitiendo a los usuarios convertir el contenido en texto.
La lista de interfaces puede ser más larga. Por ejemplo, en el plugin de draw.io:
- IMainButtonPlugin y IMainButtonItem. Se usan para implementar la acción del botón principal. En el plugin de draw.io, usamos los elementos del botón de acción -> menú Más en la sección Mis documentos o en la sala seleccionada para crear diagramas de draw.io.
- IFilePlugin y IFileItem. Se usan para interactuar con los tipos de archivo especificados. En este caso, con los archivos .drawio.
Piensa en la estructura de tu plugin. Todos los archivos requeridos están descritos en la documentación. Todo lo demás puede organizarse como prefieras. Por ejemplo, en el plugin de draw.io, colocamos el código para cada tipo de plugin en carpetas separadas, mientras que en el plugin de conversión de habla a texto, este código se coloca en la carpeta src.
Elige un nombre para tu plugin y escribe una descripción para él.
Desarrollo del plugin
Ahora que todo el trabajo de preparación está hecho, puedes comenzar a desarrollar tu plugin.
Paso 1. Crear una plantilla de plugin
Crea una plantilla de plugin y configura sus ajustes que se mostrarán en la configuración de plugins de DocSpace:
Para crear una plantilla de plugin, abre la terminal y ejecuta el siguiente comando npx:
npx create-docspace-plugin
Si el comando npx no está disponible, instala el paquete npm @onlyoffice/docspace-plugin-sdk globalmente utilizando el siguiente comando:
npm i -g @onlyoffice/docspace-plugin-sdk
Configura el plugin en la terminal especificando los ajustes en el diálogo. Todos los ajustes están descritos aquí.
Para el plugin de conversión de habla a texto, puedes usar los siguientes valores:
Puedes cambiar todos los parámetros especificados más tarde en el archivo package.json.
También puedes crear un plugin en cualquier proyecto agregando el paquete npm @onlyoffice/docspace-plugin-sdk como dependencia y especificando todos los campos necesarios en el archivo package.json.
Paso 2. Configurar el punto de entrada del plugin
El punto de entrada del plugin, index.ts, se creará automáticamente en la carpeta src durante el paso de creación de la plantilla. Este archivo es requerido.
Este archivo contiene todos los métodos básicos de los tipos de plugin que seleccionaste en el paso anterior. Puedes cambiar este archivo en cualquier momento.
Si creas un plugin por ti mismo, sin plantilla, para el punto de entrada del plugin, puedes usar el código de nuestros ejemplos de plugins listos. Funcionará perfectamente.
Paso 3. Agregar iconos del plugin
Crea la carpeta assets en la carpeta raíz del plugin y agrega allí todos los iconos del plugin. El número de iconos y sus tamaños depende de los tipos de plugins que implementes. Para el plugin de conversión de habla a texto, necesitamos los siguientes iconos:
- El tipo de plugin predeterminado requiere una imagen de logo. Esta es igual al parámetro logo del archivo package.json. El logo se mostrará en la configuración de plugins de DocSpace. El tamaño requerido del icono es 48×48 px. De lo contrario, se comprimirá a este tamaño.
- El plugin de menú contextual usa un icono en el botón Convertir a texto. El tamaño requerido del icono es 16×16 px. De lo contrario, se comprimirá a este tamaño.
Este icono también puede ser usado para el icono del botón principal. Por ejemplo, en el plugin de draw.io, el mismo icono se usa tanto para el menú contextual como para el menú del botón principal.
El plugin de draw.io también utiliza un icono específico para los archivos .drawio, que se crean con el tipo de plugin de archivo. El tamaño preferido del icono para el formato de tabla es 32×32 px.
Paso 4. Configurar los elementos de la interfaz del plugin
Por ejemplo, el plugin de draw.io contiene dos elementos principales de UI: la ventana modal y el editor de diagramas. Crea los archivos para configurar cada elemento. Para tu conveniencia, puedes poner estos archivos en una carpeta separada llamada DrawIO.
En el archivo Dialog.ts, configura los ajustes de la ventana modal. Especifica el componente IFrame que se utiliza para incrustar el sitio web de draw.io en una ventana modal:
export const frameProps: IFrame = {
width: "100%",
height: "100%",
name: "test-drawio",
src: "",
}
Crea el contenedor IBox para agregarle el iframe:
const body: IBox = {
widthProp: "100%",
heightProp: "100%",
children: [
{
component: Components.iFrame,
props: frameProps,
},
],
}
Configurar las propiedades de la ventan modal:
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,
En el archivo Editor.ts, configura el editor de diagramas. Crea la función DiagramEditor con los siguientes parámetros:
Parámetro | Tipo | Ejemplo | Descripción |
ui | string | “default” | Define el tema de la interfaz del editor. |
dark | string | “auto” | Define el tema oscuro del editor. |
off | boolean | false | Especifica si el modo offline está activo. |
lib | boolean | false | Especifica si las bibliotecas están habilitadas. |
lang | string | “auto” | Define el idioma del editor. |
url | string | `https://embed.diagrams.net` | Define la URL al editor. |
showSaveButton | boolean | true | Especifica si se mostrará el botón Guardar en el editor. |
Luego, especifica los métodos para trabajar con los diagramas:
Método | Descripción |
startEditing | Inicia el editor con los datos proporcionados. |
getData | Devuelve los datos del diagrama. |
getTitle | Devuelve el título del diagrama. |
getFormat | Devuelve el formato del diagrama. |
getFrameId | Devuelve el ID del frame del editor. |
getFrameUrl | Devuelve la URL del iframe. |
handleMessage | Maneja el mensaje proporcionado. |
initializeEditor | Envía el mensaje load al editor. |
save | Guarda los datos proporcionados. |
El código completo para el DiagramEditor se puede encontrar aquí.
Paso 5. Crear tipos de plugin
Ahora que el plugin predeterminado está listo, puedes comenzar a programar otros tipos de plugin.
Cada tipo de plugin tiene elementos específicos. Define el elemento del menú contextual que se mostrará cuando hagas clic derecho sobre archivos de audio o video:
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,
}
Agregar más tipos de plugin. Por ejemplo, el plugin draw.io puede accederse desde el menú del botón principal, por lo que necesitamos especificar el elemento del botón principal:
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],
}
Cuando se haga clic en el botón principal, aparecerá la ventana modal donde puedes escribir el nombre del diagrama y abrir un archivo vacío .drawio.
Para el plugin draw.io, también necesitarás configurar el tipo de plugin de archivo, que funciona cuando el usuario abre un archivo .drawio específico:
Define el elemento de archivo que se representa como un archivo con la extensión específica (.drawio) y un icono.
export const drawIoItem: IFileItem = {
extension: ".drawio",
fileTypeName: "Diagram",
fileRowIcon: "drawio-32.svg",
fileTileIcon: "drawio-32.svg",
devices: [Devices.desktop],
onClick,
}
Define el evento onClick que ejecutará el método editDiagram cada vez que se abra el archivo .drawio.
const onClick = async (item: File) => {
return await drawIo.editDiagram(item.id)
}
Paso 6. Crear el tipo de plugin de configuración
Configura el tipo de plugin de configuración para proporcionar a los usuarios la configuración del administrador.
Crea un contenedor donde se colocarán los ajustes del plugin:
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],
}
En la descripción de los ajustes, indica que es necesario generar un token de la API para poder trabajar con el plugin.
Configura los ajustes del administrador con el objeto 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}
}
Especifica el evento onLoad, que define qué ajustes del plugin se mostrarán cuando se cargue el bloque de configuración.
Cada elemento de configuración se determina en archivos separados (buttons, token).
Paso 7. Crear el archivo principal del código del plugin
Crea un archivo en la carpeta src con el código principal del plugin. Este archivo es obligatorio. Consulta la documentación del servicio de terceros para escribir este código.
Veamos cómo se organiza el archivo AssemblyAI.ts en detalle:
Definir la clase AssemblyAI con todas las variables y métodos necesarios:
- Variables y su descripción:
apiURL
Define la URL de la API.
apiURL = ""
currentFileId
Define el ID del archivo actual.
const currentFileId: numbernull | number = null
apiToken
Define el token de la API.
apiToken = ""
- Métodos y su descripción:
createAPIUrl
Crea la URL de la API.
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
Establece la URL de la API.
setAPIUrl = (url: string) => {
this.apiURL = url
}
getAPIUrl
Devuelve la URL de la API.
getAPIUrl = () => {
return this.apiURL
}
setAPIToken
Establece el token de la API.
setAPIToken = (apiToken: string) => {
this.apiToken = apiToken
}
getAPIToken
Devuelve el token de la API.
getAPIToken = () => {
return this.apiToken
}
fetchAPIToken
Obtiene el token de la API.
fetchAPIToken = async () => {
const apiToken = localStorage.getItem("speech-to-text-api-token")
if (!apiToken) {
return
}
this.setAPIToken(apiToken)
plugin.updateStatus(PluginStatus.active)
}
saveAPIToken
Guarda el token de la 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)
}
setCurrentFileId
Establece el ID del archivo actual.
setCurrentFileId = (id: number | null) => {
this.currentFileId = id
}
uploadFile
Sube un archivo que será transcrito.
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
Transcribe el archivo de audio.
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
Implementa la funcionalidad del plugin.
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
}
Declarar la instancia de la clase AssemblyAI:
const assemblyAI = new AssemblyAI()
Exportar la instancia del plugin creada:
export default assemblyAI
Pruebas
Para comprobar la funcionalidad del plugin y corregir cualquier error potencial, prueba el plugin:
- Construye el plugin preparado siguiendo las instrucciones aquí.
La carpeta dist se creará en la carpeta raíz del plugin y el archivo comprimido del plugin se colocará allí. Este archivo comprimido es el plugin completado que puede subirse al portal de DocSpace.
- Sube tu plugin al portal de DocSpace y prueba su apariencia y funcionalidad de manera exhaustiva.
Nota: Ten en cuenta que solo puedes subir tus propios plugins en la versión autoalojada de DocSpace
Si ocurre algún error, corrige el código fuente de tu plugin y repite el procedimiento de construcción y prueba.
Ahora que tu plugin está probado y funciona correctamente, puedes agregarlo a la versión del servidor DocSpace y comenzar a usarlo.
El plugin para DocSpace ofrece un enfoque poderoso y fácil de usar para la gestión y colaboración de documentos. Al integrarse con las plataformas preferidas por los usuarios, elimina obstáculos comunes y mejora la productividad en diferentes flujos de trabajo. Si tienes alguna pregunta sobre los plugins de DocSpace, no dudes en preguntas a nuestros desarrolladores en el foro de ONLYOFFICE. También puedes solicitar una función o reportar un error publicando un problema en GitHub o compartir tus plugins de DocSpace con nosotros y otros usuarios. ¡Te deseamos mucha suerte en tu exploración!
Crea tu cuenta gratuita de ONLYOFFICE
Visualiza, edita y colabora en documentos, hojas, diapositivas, formularios y archivos PDF en línea.