Tutorial: Crear un complemento de Outlook de composición de mensajes
Este tutorial explica cómo crear un complemento de Outlook que puede usarse en modo de composición de mensajes para insertar contenido en el cuerpo de un mensaje.
En este tutorial, aprenderá a:
- Crear un proyecto de complemento de Outlook
- Definir botones que aparecen en la ventana del mensaje de redacción
- Implementar una experiencia de primera ejecución que recopila información del usuario y recupera los datos desde un servicio externo
- Implementar un botón sin interfaz de usuario que invoca una función
- Implementar un panel de tareas que inserta contenido en el cuerpo de un mensaje
Sugerencia
Si desea una versión completa de este tutorial (con el manifiesto de solo complemento), vaya al repositorio de ejemplos de complementos de Office en GitHub.
Requisitos previos
Node.js (la última versión de LTS). Visite el sitio deNode.js para descargar e instalar la versión correcta para el sistema operativo.
La versión más reciente de Yeoman y Generador de Yeoman para complementos de Office. Para instalar estas herramientas globalmente, ejecute el siguiente comando desde el símbolo del sistema.
npm install -g yo generator-office
Nota:
Incluso si ya ha instalado el generador Yeoman, recomendamos que actualice el paquete de la versión más reciente desde npm.
Office está conectado a una suscripción Microsoft 365 (incluido Office en la Web).
Nota:
Si aún no tiene Office, puede calificar para una suscripción de desarrollador Microsoft 365 E5 a través del Programa para desarrolladores de Microsoft 365; para obtener más información, consulte las preguntas más frecuentes. Como alternativa, puede registrarse para obtener una evaluación gratuita de 1 mes o comprar un plan de Microsoft 365.
Visual Studio Code (VS Code) o el editor de código que prefiera.
Outlook en la Web, nuevo Outlook en Windows o Outlook 2016 o posterior en Windows (conectado a una cuenta de Microsoft 365).
Una cuenta de GitHub .
Instalación
El complemento que creará en este tutorial leerá gist de la cuenta GitHub del usuario y agregará el gist seleccionado al cuerpo de un mensaje. Siga los pasos siguientes para crear dos gist nuevos que puede usar para probar el complemento que va a crear.
-
En el campo Descripción del gist..., escriba Hello World Markdown.
En el campo Nombre del archivo incluye la extensión..., escriba test.md.
Agregue el markdown siguiente al cuadro de texto de varias líneas:
# Hello World This is content converted from Markdown! Here's a JSON sample: ```json { "foo": "bar" } ```
Seleccione el botón Crear gist público.
-
En el campo Descripción del gist..., escriba Hello World Html.
En el campo Nombre del archivo incluye la extensión..., escriba test.html.
Agregue el markdown siguiente al cuadro de texto de varias líneas:
<html> <head> <style> h1 { font-family: Calibri; } </style> </head> <body> <h1>Hello World!</h1> <p>This is a test</p> </body> </html>
Seleccione el botón Crear gist público.
Crear un proyecto de complemento de Outlook
Ejecute el siguiente comando para crear un proyecto de complemento con el generador Yeoman. Se agregará una carpeta que contiene el proyecto al directorio actual.
yo office
Nota:
Cuando ejecute el comando
yo office
, es posible que reciba mensajes sobre las directivas de recopilación de datos de Yeoman y las herramientas de la CLI de complementos de Office. Use la información adecuada que se proporciona para responder a los mensajes.Cuando se le pida, proporcione la siguiente información para crear el proyecto de complemento.
Los pasos para crear el proyecto varían ligeramente en función del tipo de manifiesto.
Nota:
El manifiesto unificado para Microsoft 365 le permite combinar un complemento de Outlook con una aplicación de Teams como una sola unidad de desarrollo e implementación. Estamos trabajando para ampliar la compatibilidad con el manifiesto unificado a Excel, PowerPoint, Word, desarrollo de Copilot personalizado y otras extensiones de Microsoft 365. Para más información, consulte Complementos de Office con el manifiesto unificado. Para obtener un ejemplo de una aplicación de Teams combinada y un complemento de Outlook, consulte Ofertas de descuento.
Nos encanta recibir sus comentarios sobre el manifiesto unificado. Si tiene alguna sugerencia, cree un problema en el repositorio para la biblioteca de JavaScript de Office.
Elija un tipo de proyecto -
Office Add-in Task Pane project
Elija un tipo de script -
JavaScript
¿Qué nombre quiere asignar al complemento? -
Git the gist
¿Qué aplicación cliente de Office desea admitir? -
Outlook
¿Qué manifiesto le gustaría usar? -
unified manifest for Microsoft 365
Después de completar el asistente, el generador creará el proyecto e instalará componentes auxiliares de Node.
Vaya a la carpeta raíz del proyecto.
cd "Git the gist"
Este complemento usa las siguientes bibliotecas.
- La biblioteca Showdown para convertir el archivo Markdown a HTML.
- La biblioteca URI.js para crear direcciones URL relativas.
- Biblioteca de jQuery para simplificar las interacciones dom.
Para instalar estas herramientas para el proyecto, ejecute el comando siguiente en el directorio raíz del proyecto.
npm install showdown urijs jquery --save
Abra el proyecto en VS Code o en el editor de código que prefiera.
Sugerencia
En Windows, puede navegar hasta el directorio raíz del proyecto desde la línea de comandos y, después, puede escribir
code .
para abrir esa carpeta en VS Code. En un equipo Mac, necesitará agregar el comandocode
a la ruta de acceso antes de poder usar ese comando para abrir la carpeta de proyecto en VS Code.
Actualizar el manifiesto
El manifiesto de un complemento controla cómo se muestra en Outlook. Define cómo se muestran el complemento en la lista de complementos y los botones que aparecen en la cinta de opciones, y establece las direcciones URL de los archivos HTML y JavaScript que ese complemento usa.
Especificar información básica
Realice las siguientes actualizaciones en el archivo de manifiesto para especificar información básica sobre el complemento.
Busque la propiedad "description", reemplace los valores predeterminados "short" y "long" por las descripciones del complemento y guarde el archivo.
"description": { "short": "Gets gists.", "full": "Allows users to access their GitHub gists." },
Guarde el archivo.
Probar el complemento generado
Antes de seguir, vamos a probar el complemento básico que creó el generador para confirmar que el proyecto está configurado correctamente.
Nota:
Los complementos de Office deben usar HTTPS, no HTTP, aunque esté desarrollando. Si se le pide que instale un certificado después de ejecutar uno de los siguientes comandos, acepte el mensaje para instalar el certificado que proporciona el generador de Yeoman. Es posible que también deba ejecutar el símbolo del sistema o el terminal como administrador para que se realicen los cambios.
Si es la primera vez que desarrolla un complemento de Office en el equipo, es posible que se le pida en la línea de comandos que conceda a Microsoft Edge WebView una exención de bucle invertido ("Allow localhost loopback for Microsoft Edge WebView?"). Cuando se le solicite, escriba
Y
para permitir la exención. Tenga en cuenta que necesitará privilegios de administrador para permitir la exención. Una vez permitido, no se le pedirá una exención al transferir localmente complementos de Office en el futuro (a menos que quite la exención de la máquina). Para obtener más información, consulte "No se puede abrir este complemento desde localhost" al cargar un complemento de Office o mediante Fiddler.
Ejecute el siguiente comando en el directorio raíz del proyecto. Al ejecutar este comando, se inicia el servidor web local y el complemento se carga de forma local.
npm start
Nota:
Si el complemento no se ha descargado de forma local automáticamente, siga las instrucciones de Transferencia local de complementos de Outlook para realizar pruebas para transferir manualmente el complemento en Outlook.
En Outlook, abra un mensaje existente y seleccione el botón Mostrar panel de tareas.
Cuando se le solicite el cuadro de diálogo WebView Stop On Load, seleccione Aceptar.
Nota:
Si selecciona Cancelar, no se volverá a mostrar el cuadro de diálogo mientras se esté ejecutando esta instancia del complemento. Sin embargo, si reinicia el complemento, verá el cuadro de diálogo de nuevo.
Si todo se ha configurado correctamente, el panel de tareas se abre y representa la página principal del complemento.
Si desea detener el servidor web local y desinstalar el complemento, siga las instrucciones aplicables:
Para detener el servidor, ejecute el siguiente comando. Si usó
npm start
, el siguiente comando también debe desinstalar el complemento.npm stop
Si descargó manualmente el complemento de forma local, consulte Eliminación de un complemento cargado localmente.
Definir botones
Ahora que ya ha comprobado que el complemento base funciona, puede personalizarlo para agregar más funcionalidades. De forma predeterminada, el manifiesto define solo los botones de la ventana de mensaje leído. Vamos a actualizar el manifiesto para eliminar los botones de la ventana de mensaje leído y definir los dos nuevos botones de la ventana de composición de mensaje:
Insertar gist: un botón que abre un panel de tareas
Insertar gist predeterminado: un botón que invoca una función
El procedimiento depende del manifiesto que use.
Siga estos pasos:
Abra el archivo manifest.json .
En la matriz "extensions.runtimes", hay dos objetos en tiempo de ejecución. Para el segundo, con el "id" de "CommandsRuntime", cambie "actions.id" a "insertDefaultGist". Este es el nombre de una función que se crea en un paso posterior. Cuando haya terminado, el objeto en tiempo de ejecución debe tener un aspecto similar al siguiente:
{ "id": "CommandsRuntime", "type": "general", "code": { "page": "https://localhost:3000/commands.html", "script": "https://localhost:3000/commands.js" }, "lifetime": "short", "actions": [ { "id": "insertDefaultGist", "type": "executeFunction", "displayName": "action" } ] }
Cambie el elemento de la matriz "extensions.ribbons.contexts" a "mailCompose". Esto significa que los botones solo aparecerán en una nueva ventana de mensaje o respuesta.
"contexts": [ "mailCompose" ],
La matriz "extensions.ribbons.tabs.groups" tiene un objeto de grupo. Realice los siguientes cambios en este objeto.
- Cambie la propiedad "id" a "msgComposeCmdGroup".
- Cambie la propiedad "label" a "Git the gist".
Ese mismo objeto de grupo tiene una matriz de "controles" con dos objetos de control. Es necesario realizar cambios en el JSON para cada uno de ellos. En el primero, realice estos pasos.
- Cambie "id" a "msgComposeInsertGist".
- Cambie la "etiqueta" a "Insertar gist".
- Cambie "supertip.title" a "Insert gist".
- Cambie "supertip.description" a "Muestra una lista de los gists y le permite insertar su contenido en el mensaje actual".
En el segundo objeto de control, realice estos pasos.
- Cambie "id" a "msgComposeInsertDefaultGist".
- Cambie la "etiqueta" a "Insertar gist predeterminado".
- Cambie "supertip.title" a "Insert default gist".
- Cambie "supertip.description" a "Inserts the content of the gist you mark as default into the current message".
- Cambie "actionId" a "insertDefaultGist". Esto coincide con el "action.id" de "CommandsRuntime" que estableció en un paso anterior.
Cuando haya terminado, la propiedad "ribbons" debe tener un aspecto similar al siguiente:
"ribbons": [ { "contexts": [ "mailCompose" ], "tabs": [ { "builtInTabId": "TabDefault", "groups": [ { "id": "msgComposeCmdGroup", "label": "Git the gist", "icons": [ { "size": 16, "file": "https://localhost:3000/assets/icon-16.png" }, { "size": 32, "file": "https://localhost:3000/assets/icon-32.png" }, { "size": 80, "file": "https://localhost:3000/assets/icon-80.png" } ], "controls": [ { "id": "msgComposeInsertGist", "type": "button", "label": "Insert gist", "icons": [ { "size": 16, "file": "https://localhost:3000/assets/icon-16.png" }, { "size": 32, "file": "https://localhost:3000/assets/icon-32.png" }, { "size": 80, "file": "https://localhost:3000/assets/icon-80.png" } ], "supertip": { "title": "Insert gist", "description": "Displays a list of your gists and allows you to insert their contents into the current message." }, "actionId": "TaskPaneRuntimeShow" }, { "id": "msgComposeInsertDefaultGist", "type": "button", "label": "Insert default gist", "icons": [ { "size": 16, "file": "https://localhost:3000/assets/icon-16.png" }, { "size": 32, "file": "https://localhost:3000/assets/icon-32.png" }, { "size": 80, "file": "https://localhost:3000/assets/icon-80.png" } ], "supertip": { "title": "Insert default gist", "description": "Inserts the content of the gist you mark as default into the current message." }, "actionId": "insertDefaultGist" } ] } ] } ] } ]
Guarde los cambios en el manifiesto.
Reinstalar el complemento
Debe reinstalar el complemento para que los cambios del manifiesto surtan efecto.
Si el servidor web se está ejecutando, ejecute el siguiente comando.
npm stop
Ejecute el siguiente comando para iniciar el servidor web local y transferir local y automáticamente el complemento.
npm start
Después de volver a instalar el complemento, puede comprobar que se ha instalado correctamente buscando los comandos Insertar gist e Insertar gist predeterminado en la ventana de composición de mensaje. Tenga en cuenta que no sucederá nada si selecciona uno de estos elementos, ya que aún no ha terminado de crear este complemento.
Si ejecuta este complemento en Outlook 2016 o versiones posteriores en Windows, debería ver dos botones nuevos en la cinta de opciones de la ventana del mensaje de redacción: Insertar gist e Insertar gist predeterminado.
Si ejecuta este complemento en Outlook en la Web o nuevo Outlook en Windows, seleccione Aplicaciones en la cinta de opciones de la ventana del mensaje de redacción y, a continuación, seleccione Git el gist para ver las opciones Insertar gist e Insertar gist predeterminado.
Implementar una experiencia de primera ejecución
Este complemento debe poder leer gist desde la cuenta GitHub del usuario e identificar cuál ha elegido el usuario como gist predeterminado. Para lograr estos objetivos, el complemento debe pedir al usuario que proporcione su nombre de usuario de GitHub y seleccione un gist predeterminado desde su colección de gist existentes. Complete los pasos de esta sección para implementar una experiencia de primera ejecución que muestre un cuadro de diálogo para recopilar esta información del usuario.
Creación de la interfaz de usuario del cuadro de diálogo
Comencemos creando la interfaz de usuario para el cuadro de diálogo.
En la carpeta ./src, cree una subcarpeta denominada configuración.
En la carpeta ./src/settings , cree un archivo denominado dialog.html.
En dialog.html, agregue el marcado siguiente para definir un formulario básico con una entrada de texto para un nombre de usuario de GitHub y una lista vacía para gists que se rellenará a través de JavaScript.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <title>Settings</title> <!-- Office JavaScript API --> <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"></script> <!-- For more information on Fluent UI, visit https://developer.microsoft.com/fluentui. --> <link rel="stylesheet" href="https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/office-ui-fabric-core/11.0.0/css/fabric.min.css"/> <!-- Template styles --> <link href="dialog.css" rel="stylesheet" type="text/css" /> </head> <body class="ms-font-l"> <main> <section class="ms-font-m ms-fontColor-neutralPrimary"> <div class="not-configured-warning ms-MessageBar ms-MessageBar--warning"> <div class="ms-MessageBar-content"> <div class="ms-MessageBar-icon"> <i class="ms-Icon ms-Icon--Info"></i> </div> <div class="ms-MessageBar-text"> Oops! It looks like you haven't configured <strong>Git the gist</strong> yet. <br/> Please configure your GitHub username and select a default gist, then try that action again! </div> </div> </div> <div class="ms-font-xxl">Settings</div> <div class="ms-Grid"> <div class="ms-Grid-row"> <div class="ms-TextField"> <label class="ms-Label">GitHub Username</label> <input class="ms-TextField-field" id="github-user" type="text" value="" placeholder="Please enter your GitHub username"> </div> </div> <div class="error-display ms-Grid-row"> <div class="ms-font-l ms-fontWeight-semibold">An error occurred:</div> <pre><code id="error-text"></code></pre> </div> <div class="gist-list-container ms-Grid-row"> <div class="list-title ms-font-xl ms-fontWeight-regular">Choose Default Gist</div> <form> <div id="gist-list"> </div> </form> </div> </div> <div class="ms-Dialog-actions"> <div class="ms-Dialog-actionsRight"> <button class="ms-Dialog-action ms-Button ms-Button--primary" id="settings-done" disabled> <span class="ms-Button-label">Done</span> </button> </div> </div> </section> </main> <script type="text/javascript" src="../../node_modules/jquery/dist/jquery.js"></script> <script type="text/javascript" src="../helpers/gist-api.js"></script> <script type="text/javascript" src="dialog.js"></script> </body> </html>
Quizás haya observado que el archivo HTML hace referencia a un archivo JavaScript, gist-api.js, que aún no existe. Este archivo se creará en la sección siguiente Capturar datos de GitHub.
Guarde los cambios.
A continuación, cree un archivo en la carpeta ./src/settings denominada dialog.css.
En dialog.css, agregue el código siguiente para especificar los estilos que usa dialog.html.
section { margin: 10px 20px; } .not-configured-warning { display: none; } .error-display { display: none; } .gist-list-container { margin: 10px -8px; display: none; } .list-title { border-bottom: 1px solid #a6a6a6; padding-bottom: 5px; } ul { margin-top: 10px; } .ms-ListItem-secondaryText, .ms-ListItem-tertiaryText { padding-left: 15px; }
Guarde los cambios.
Desarrollar la funcionalidad del cuadro de diálogo
Ahora que ha definido el cuadro de diálogo de interfaz de usuario, puede escribir el código que le permite funcionar.
En la carpeta ./src/settings , cree un archivo denominado dialog.js.
Agregue el siguiente código. Tenga en cuenta que este código usa jQuery para registrar eventos y usa el método
messageParent
para devolver las opciones del usuario al autor de la llamada.(function() { 'use strict'; // The onReady function must be run each time a new page is loaded. Office.onReady(function() { $(document).ready(function() { if (window.location.search) { // Check if warning should be displayed. const warn = getParameterByName('warn'); if (warn) { $('.not-configured-warning').show(); } else { // See if the config values were passed. // If so, pre-populate the values. const user = getParameterByName('gitHubUserName'); const gistId = getParameterByName('defaultGistId'); $('#github-user').val(user); loadGists(user, function(success) { if (success) { $('.ms-ListItem').removeClass('is-selected'); $('input').filter(function() { return this.value === gistId; }).addClass('is-selected').attr('checked', 'checked'); $('#settings-done').removeAttr('disabled'); } }); } } // When the GitHub username changes, // try to load gists. $('#github-user').on('change', function() { $('#gist-list').empty(); const ghUser = $('#github-user').val(); if (ghUser.length > 0) { loadGists(ghUser); } }); // When the Done button is selected, send the // values back to the caller as a serialized // object. $('#settings-done').on('click', function() { const settings = {}; settings.gitHubUserName = $('#github-user').val(); const selectedGist = $('.ms-ListItem.is-selected'); if (selectedGist) { settings.defaultGistId = selectedGist.val(); sendMessage(JSON.stringify(settings)); } }); }); }); // Load gists for the user using the GitHub API // and build the list. function loadGists(user, callback) { getUserGists(user, function(gists, error) { if (error) { $('.gist-list-container').hide(); $('#error-text').text(JSON.stringify(error, null, 2)); $('.error-display').show(); if (callback) callback(false); } else { $('.error-display').hide(); buildGistList($('#gist-list'), gists, onGistSelected); $('.gist-list-container').show(); if (callback) callback(true); } }); } function onGistSelected() { $('.ms-ListItem').removeClass('is-selected').removeAttr('checked'); $(this).children('.ms-ListItem').addClass('is-selected').attr('checked', 'checked'); $('.not-configured-warning').hide(); $('#settings-done').removeAttr('disabled'); } function sendMessage(message) { Office.context.ui.messageParent(message); } function getParameterByName(name, url) { if (!url) { url = window.location.href; } name = name.replace(/[\[\]]/g, "\\$&"); const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, " ")); } })();
Guarde los cambios.
Actualizar los valores de configuración de webpack
Por último, abra el archivo webpack.config.js en el directorio raíz del proyecto y siga los pasos siguientes.
Busque el objeto
entry
en el objetoconfig
y agregue una nueva entrada paradialog
.dialog: "./src/settings/dialog.js",
Una vez hecho esto, el nuevo objeto
entry
tendrá el siguiente aspecto:entry: { polyfill: ["core-js/stable", "regenerator-runtime/runtime"], taskpane: ["./src/taskpane/taskpane.js", "./src/taskpane/taskpane.html"], commands: "./src/commands/commands.js", dialog: "./src/settings/dialog.js", },
Busque la matriz
plugins
en el objetoconfig
. En la matrizpatterns
del objetonew CopyWebpackPlugin
, agregue nuevas entradas para taskpane.css y dialog.css.{ from: "./src/taskpane/taskpane.css", to: "taskpane.css", }, { from: "./src/settings/dialog.css", to: "dialog.css", },
Una vez hecho esto, el objeto tendrá un
new CopyWebpackPlugin
aspecto similar al siguiente. Tenga en cuenta la ligera diferencia si el complemento usa el manifiesto de solo complemento.new CopyWebpackPlugin({ patterns: [ { from: "./src/taskpane/taskpane.css", to: "taskpane.css", }, { from: "./src/settings/dialog.css", to: "dialog.css", }, { from: "assets/*", to: "assets/[name][ext][query]", }, { from: "manifest*.json", // The file extension is "xml" if the add-in only manifest is being used. to: "[name]" + "[ext]", transform(content) { if (dev) { return content; } else { return content.toString().replace(new RegExp(urlDev, "g"), urlProd); } }, }, ]}),
En la misma matriz
plugins
dentro del objetoconfig
, agregue el nuevo objeto al final de esa matriz.new HtmlWebpackPlugin({ filename: "dialog.html", template: "./src/settings/dialog.html", chunks: ["polyfill", "dialog"] })
Una vez hecho esto, la nueva
plugins
matriz tendrá el siguiente aspecto. Tenga en cuenta la ligera diferencia si el complemento usa el manifiesto de solo complemento.plugins: [ new HtmlWebpackPlugin({ filename: "taskpane.html", template: "./src/taskpane/taskpane.html", chunks: ["polyfill", "taskpane"], }), new CopyWebpackPlugin({ patterns: [ { from: "./src/taskpane/taskpane.css", to: "taskpane.css", }, { from: "./src/settings/dialog.css", to: "dialog.css", }, { from: "assets/*", to: "assets/[name][ext][query]", }, { from: "manifest*.json", // The file extension is "xml" if the add-in only manifest is being used. to: "[name]." + buildType + "[ext]", transform(content) { if (dev) { return content; } else { return content.toString().replace(new RegExp(urlDev, "g"), urlProd); } }, }, ], }), new HtmlWebpackPlugin({ filename: "commands.html", template: "./src/commands/commands.html", chunks: ["polyfill", "commands"], }), new HtmlWebpackPlugin({ filename: "dialog.html", template: "./src/settings/dialog.html", chunks: ["polyfill", "dialog"] }) ],
Extraer datos de GitHub
El archivo dialog.js que acaba de crear especifica que el complemento debe cargar gist cuando se active el evento change para el campo de nombre de usuario de GitHub. Para recuperar los gist de usuario desde GitHub, usará la API de gist de GitHub.
En la carpeta ./src, cree una subcarpeta denominada auxiliares.
En la carpeta ./src/helpers , cree un archivo denominado gist-api.js.
En gist-api.js, agregue el código siguiente para recuperar los gists del usuario de GitHub y compilar la lista de gists.
function getUserGists(user, callback) { const requestUrl = 'https://api.github.com/users/' + user + '/gists'; $.ajax({ url: requestUrl, dataType: 'json' }).done(function(gists) { callback(gists); }).fail(function(error) { callback(null, error); }); } function buildGistList(parent, gists, clickFunc) { gists.forEach(function(gist) { const listItem = $('<div/>') .appendTo(parent); const radioItem = $('<input>') .addClass('ms-ListItem') .addClass('is-selectable') .attr('type', 'radio') .attr('name', 'gists') .attr('tabindex', 0) .val(gist.id) .appendTo(listItem); const descPrimary = $('<span/>') .addClass('ms-ListItem-primaryText') .text(gist.description) .appendTo(listItem); const descSecondary = $('<span/>') .addClass('ms-ListItem-secondaryText') .text(' - ' + buildFileList(gist.files)) .appendTo(listItem); const updated = new Date(gist.updated_at); const descTertiary = $('<span/>') .addClass('ms-ListItem-tertiaryText') .text(' - Last updated ' + updated.toLocaleString()) .appendTo(listItem); listItem.on('click', clickFunc); }); } function buildFileList(files) { let fileList = ''; for (let file in files) { if (files.hasOwnProperty(file)) { if (fileList.length > 0) { fileList = fileList + ', '; } fileList = fileList + files[file].filename + ' (' + files[file].language + ')'; } } return fileList; }
Guarde los cambios.
Ejecute el siguiente comando para volver a compilar el proyecto.
npm run build
Implementar un botón sin interfaz de usuario
El botón Insertar gist predeterminado de este complemento es un botón sin interfaz de usuario que invoca una función de JavaScript, en lugar de abrir un panel de tareas como hacen muchos botones de complemento. Cuando el usuario selecciona el botón Insertar gist predeterminado , la función de JavaScript correspondiente comprueba si se ha configurado el complemento.
Si el complemento ya se ha configurado, la función carga el contenido del gist que el usuario ha seleccionado como valor predeterminado e lo inserta en el cuerpo del mensaje.
Si el complemento aún no se ha configurado, el cuadro de diálogo de configuración solicita al usuario que proporcione la información necesaria.
Actualizar el archivo de la función (HTML)
Una función invocada por un botón sin interfaz de usuario debe definirse en el archivo especificado por el <elemento FunctionFile> en el manifiesto para el factor de forma correspondiente. El manifiesto de este complemento especifica https://localhost:3000/commands.html
como el archivo de la función.
Abra el archivo ./src/commands/commands.html y reemplace todo el contenido por el marcado siguiente.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <!-- Office JavaScript API --> <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"></script> <script type="text/javascript" src="../../node_modules/jquery/dist/jquery.js"></script> <script type="text/javascript" src="../../node_modules/showdown/dist/showdown.min.js"></script> <script type="text/javascript" src="../../node_modules/urijs/src/URI.min.js"></script> <script type="text/javascript" src="../helpers/addin-config.js"></script> <script type="text/javascript" src="../helpers/gist-api.js"></script> </head> <body> <!-- NOTE: The body is empty on purpose. Since functions in commands.js are invoked via a button, there is no UI to render. --> </body> </html>
Quizás haya observado que el archivo HTML hace referencia a un archivo JavaScript, addin-config.js, que aún no existe. Este archivo se creará en la sección Crear un archivo para administrar las opciones de configuración más adelante en este tutorial.
Guarde los cambios.
Actualizar el archivo de la función (JavaScript)
Abra el archivo ./src/commands/commands.js y reemplace todo el contenido con el siguiente código. Tenga en cuenta que si la función insertDefaultGist determina que el complemento aún no se ha configurado, agrega el
?warn=1
parámetro a la dirección URL del cuadro de diálogo. Al hacer eso, el cuadro de diálogo de configuración representa la barra de mensajes que se define en ./src/settings/dialog.html, para indicar al usuario por qué está viendo el cuadro de diálogo.let config; let btnEvent; // The onReady function must be run each time a new page is loaded. Office.onReady(); function showError(error) { Office.context.mailbox.item.notificationMessages.replaceAsync('github-error', { type: 'errorMessage', message: error }); } let settingsDialog; function insertDefaultGist(event) { config = getConfig(); // Check if the add-in has been configured. if (config && config.defaultGistId) { // Get the default gist content and insert. try { getGist(config.defaultGistId, function(gist, error) { if (gist) { buildBodyContent(gist, function (content, error) { if (content) { Office.context.mailbox.item.body.setSelectedDataAsync( content, { coercionType: Office.CoercionType.Html }, function (result) { event.completed(); } ); } else { showError(error); event.completed(); } }); } else { showError(error); event.completed(); } }); } catch (err) { showError(err); event.completed(); } } else { // Save the event object so we can finish up later. btnEvent = event; // Not configured yet, display settings dialog with // warn=1 to display warning. const url = new URI('dialog.html?warn=1').absoluteTo(window.location).toString(); const dialogOptions = { width: 20, height: 40, displayInIframe: true }; Office.context.ui.displayDialogAsync(url, dialogOptions, function(result) { settingsDialog = result.value; settingsDialog.addEventHandler(Office.EventType.DialogMessageReceived, receiveMessage); settingsDialog.addEventHandler(Office.EventType.DialogEventReceived, dialogClosed); }); } } // Register the function. Office.actions.associate("insertDefaultGist", insertDefaultGist); function receiveMessage(message) { config = JSON.parse(message.message); setConfig(config, function(result) { settingsDialog.close(); settingsDialog = null; btnEvent.completed(); btnEvent = null; }); } function dialogClosed(message) { settingsDialog = null; btnEvent.completed(); btnEvent = null; }
Guarde los cambios.
Crear un archivo para administrar la configuración
En la carpeta ./src/helpers, cree un archivo denominado addin-config.js y agregue el código siguiente. El código usa el objeto RoamingSettings para obtener y establecer los valores de configuración.
function getConfig() { const config = {}; config.gitHubUserName = Office.context.roamingSettings.get('gitHubUserName'); config.defaultGistId = Office.context.roamingSettings.get('defaultGistId'); return config; } function setConfig(config, callback) { Office.context.roamingSettings.set('gitHubUserName', config.gitHubUserName); Office.context.roamingSettings.set('defaultGistId', config.defaultGistId); Office.context.roamingSettings.saveAsync(callback); }
Guarde los cambios.
Crear funciones nuevas para procesar los gist
Abra el archivo ./src/helpers/gist-api.js y agregue las siguientes funciones. Tenga en cuenta lo siguiente:
Si el gist contiene HTML, el complemento inserta el HTML tal y como está en el cuerpo del mensaje.
Si el gist contiene Markdown, el complemento usa la biblioteca Showdown para convertir Markdown en HTML y, a continuación, inserta el HTML resultante en el cuerpo del mensaje.
Si el gist contiene algo que no sea un HTML o Markdown, el complemento lo inserta en el cuerpo del mensaje como un fragmento de código.
function getGist(gistId, callback) { const requestUrl = 'https://api.github.com/gists/' + gistId; $.ajax({ url: requestUrl, dataType: 'json' }).done(function(gist) { callback(gist); }).fail(function(error) { callback(null, error); }); } function buildBodyContent(gist, callback) { // Find the first non-truncated file in the gist // and use it. for (let filename in gist.files) { if (gist.files.hasOwnProperty(filename)) { const file = gist.files[filename]; if (!file.truncated) { // We have a winner. switch (file.language) { case 'HTML': // Insert as is. callback(file.content); break; case 'Markdown': // Convert Markdown to HTML. const converter = new showdown.Converter(); const html = converter.makeHtml(file.content); callback(html); break; default: // Insert contents as a <code> block. let codeBlock = '<pre><code>'; codeBlock = codeBlock + file.content; codeBlock = codeBlock + '</code></pre>'; callback(codeBlock); } return; } } } callback(null, 'No suitable file found in the gist'); }
Guarde los cambios.
Probar el botón Insertar gist predeterminado
Si el servidor web local aún no se está ejecutando, ejecute
npm start
desde el símbolo del sistema.Abra Outlook y redacte un mensaje nuevo.
En la ventana de composición de mensaje, seleccione el botón Insertar gist predeterminado. Se mostrará un cuadro de diálogo donde puede configurar el complemento, empezando por la solicitud para configurar el nombre de usuario de GitHub.
En el cuadro de diálogo de configuración, escriba el nombre de usuario de GitHub y, a continuación, haga clic en Tab o en otra parte del cuadro de diálogo para invocar el evento de cambio , que debe cargar la lista de gists públicos. Seleccione un gist para establecerlo como predeterminado y seleccione Listo.
Vuelva a seleccionar el botón Insertar gist predeterminado. Esta vez, debería ver el contenido del gist insertado en el cuerpo del correo electrónico.
Nota:
Outlook en Windows: para elegir la configuración más reciente, deberá cerrar y volver a abrir la ventana de redacción del mensaje.
Implementar un panel de tareas
El botón Insertar gist de este complemento abre un panel de tareas y muestra los gists del usuario. Luego, el usuario puede seleccionar uno de los gist para insertarlo en el cuerpo del mensaje. Si el usuario no ha configurado el complemento todavía, se le pedirá que lo haga.
Especifique el código HTML para el panel de tareas
En el proyecto que ha creado, el panel de tareas HTML se especifica en el archivo ./src/taskpane/taskpane.html. Abra el archivo y reemplace todo el contenido con el marcado siguiente.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Contoso Task Pane Add-in</title> <!-- Office JavaScript API --> <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"></script> <!-- For more information on Fluent UI, visit https://developer.microsoft.com/fluentui. --> <link rel="stylesheet" href="https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/office-ui-fabric-core/11.0.0/css/fabric.min.css"/> <!-- Template styles --> <link href="taskpane.css" rel="stylesheet" type="text/css" /> </head> <body class="ms-font-l ms-landing-page"> <main class="ms-landing-page__main"> <section class="ms-landing-page__content ms-font-m ms-fontColor-neutralPrimary"> <div id="not-configured" style="display: none;"> <div class="centered ms-font-xxl ms-u-textAlignCenter">Welcome!</div> <div class="ms-font-xl" id="settings-prompt">Please choose the <strong>Settings</strong> icon at the bottom of this window to configure this add-in.</div> </div> <div id="gist-list-container" style="display: none;"> <form> <div id="gist-list"> </div> </form> </div> <div id="error-display" style="display: none;" class="ms-u-borderBase ms-fontColor-error ms-font-m ms-bgColor-error ms-borderColor-error"> </div> </section> <button class="ms-Button ms-Button--primary" id="insert-button" tabindex=0 disabled> <span class="ms-Button-label">Insert</span> </button> </main> <footer class="ms-landing-page__footer ms-bgColor-themePrimary"> <div class="ms-landing-page__footer--left"> <img src="../../assets/logo-filled.png" /> <h1 class="ms-font-xl ms-fontWeight-semilight ms-fontColor-white">Git the gist</h1> </div> <div id="settings-icon" class="ms-landing-page__footer--right" aria-label="Settings" tabindex=0> <i class="ms-Icon enlarge ms-Icon--Settings ms-fontColor-white"></i> </div> </footer> <script type="text/javascript" src="../../node_modules/jquery/dist/jquery.js"></script> <script type="text/javascript" src="../../node_modules/showdown/dist/showdown.min.js"></script> <script type="text/javascript" src="../../node_modules/urijs/src/URI.min.js"></script> <script type="text/javascript" src="../helpers/addin-config.js"></script> <script type="text/javascript" src="../helpers/gist-api.js"></script> <script type="text/javascript" src="taskpane.js"></script> </body> </html>
Guarde los cambios.
Especifique el CSS para el panel de tareas
En el proyecto que ha creado, el panel de tareas CSS se especifica en el archivo ./src/taskpane/taskpane.css. Abra el archivo y reemplace todo el contenido con el código siguiente.
/* Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license in root of repo. */ html, body { width: 100%; height: 100%; margin: 0; padding: 0; overflow: auto; } body { position: relative; font-size: 16px; } main { height: 100%; overflow-y: auto; } footer { width: 100%; position: relative; bottom: 0; margin-top: 10px;} p, h1, h2, h3, h4, h5, h6 { margin: 0; padding: 0; } ul { padding: 0; } #settings-prompt { margin: 10px 0; } #error-display { padding: 10px; } #insert-button { margin: 0 10px; } .clearfix { display: block; clear: both; height: 0; } .pointerCursor { cursor: pointer; } .invisible { visibility: hidden; } .undisplayed { display: none; } .ms-Icon.enlarge { position: relative; font-size: 20px; top: 4px; } .ms-ListItem-secondaryText, .ms-ListItem-tertiaryText { padding-left: 15px; } .ms-landing-page { display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; -webkit-flex-wrap: nowrap; flex-wrap: nowrap; height: 100%; } .ms-landing-page__main { display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; -webkit-flex-wrap: nowrap; flex-wrap: nowrap; -webkit-flex: 1 1 0; flex: 1 1 0; height: 100%; } .ms-landing-page__content { display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; -webkit-flex-wrap: nowrap; flex-wrap: nowrap; height: 100%; -webkit-flex: 1 1 0; flex: 1 1 0; padding: 20px; } .ms-landing-page__content h2 { margin-bottom: 20px; } .ms-landing-page__footer { display: -webkit-inline-flex; display: inline-flex; -webkit-justify-content: center; justify-content: center; -webkit-align-items: center; align-items: center; } .ms-landing-page__footer--left { transition: background ease 0.1s, color ease 0.1s; display: -webkit-inline-flex; display: inline-flex; -webkit-justify-content: flex-start; justify-content: flex-start; -webkit-align-items: center; align-items: center; -webkit-flex: 1 0 0px; flex: 1 0 0px; padding: 20px; } .ms-landing-page__footer--left:active { cursor: default; } .ms-landing-page__footer--left--disabled { opacity: 0.6; pointer-events: none; cursor: not-allowed; } .ms-landing-page__footer--left--disabled:active, .ms-landing-page__footer--left--disabled:hover { background: transparent; } .ms-landing-page__footer--left img { width: 40px; height: 40px; } .ms-landing-page__footer--left h1 { -webkit-flex: 1 0 0px; flex: 1 0 0px; margin-left: 15px; text-align: left; width: auto; max-width: auto; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ms-landing-page__footer--right { transition: background ease 0.1s, color ease 0.1s; padding: 29px 20px; } .ms-landing-page__footer--right:active, .ms-landing-page__footer--right:hover { background: #005ca4; cursor: pointer; } .ms-landing-page__footer--right:active { background: #005ca4; } .ms-landing-page__footer--right--disabled { opacity: 0.6; pointer-events: none; cursor: not-allowed; } .ms-landing-page__footer--right--disabled:active, .ms-landing-page__footer--right--disabled:hover { background: transparent; }
Guarde los cambios.
Especifique el JavaScript para el panel de tareas
En el proyecto que ha creado, el panel de tareas JavaScript se especifica en el archivo ./src/taskpane/taskpane.js. Abra el archivo y reemplace todo el contenido con el código siguiente.
(function() { 'use strict'; let config; let settingsDialog; Office.onReady(function() { $(document).ready(function() { config = getConfig(); // Check if add-in is configured. if (config && config.gitHubUserName) { // If configured, load the gist list. loadGists(config.gitHubUserName); } else { // Not configured yet. $('#not-configured').show(); } // When insert button is selected, build the content // and insert into the body. $('#insert-button').on('click', function() { const gistId = $('.ms-ListItem.is-selected').val(); getGist(gistId, function(gist, error) { if (gist) { buildBodyContent(gist, function (content, error) { if (content) { Office.context.mailbox.item.body.setSelectedDataAsync( content, { coercionType: Office.CoercionType.Html }, function (result) { if (result.status === Office.AsyncResultStatus.Failed) { showError("Could not insert gist: " + result.error.message); } } ); } else { showError('Could not create insertable content: ' + error); } }); } else { showError('Could not retrieve gist: ' + error); } }); }); // When the settings icon is selected, open the settings dialog. $('#settings-icon').on('click', function() { // Display settings dialog. let url = new URI('dialog.html').absoluteTo(window.location).toString(); if (config) { // If the add-in has already been configured, pass the existing values // to the dialog. url = url + '?gitHubUserName=' + config.gitHubUserName + '&defaultGistId=' + config.defaultGistId; } const dialogOptions = { width: 20, height: 40, displayInIframe: true }; Office.context.ui.displayDialogAsync(url, dialogOptions, function(result) { settingsDialog = result.value; settingsDialog.addEventHandler(Office.EventType.DialogMessageReceived, receiveMessage); settingsDialog.addEventHandler(Office.EventType.DialogEventReceived, dialogClosed); }); }) }); }); function loadGists(user) { $('#error-display').hide(); $('#not-configured').hide(); $('#gist-list-container').show(); getUserGists(user, function(gists, error) { if (error) { } else { $('#gist-list').empty(); buildGistList($('#gist-list'), gists, onGistSelected); } }); } function onGistSelected() { $('#insert-button').removeAttr('disabled'); $('.ms-ListItem').removeClass('is-selected').removeAttr('checked'); $(this).children('.ms-ListItem').addClass('is-selected').attr('checked', 'checked'); } function showError(error) { $('#not-configured').hide(); $('#gist-list-container').hide(); $('#error-display').text(error); $('#error-display').show(); } function receiveMessage(message) { config = JSON.parse(message.message); setConfig(config, function(result) { settingsDialog.close(); settingsDialog = null; loadGists(config.gitHubUserName); }); } function dialogClosed(message) { settingsDialog = null; } })();
Guarde los cambios.
Probar el botón Insertar gist
Si el servidor web local aún no se está ejecutando, ejecute
npm start
desde el símbolo del sistema.Abra Outlook y redacte un mensaje nuevo.
En la ventana de composición de mensaje, seleccione el botón Insertar gist. Verá un panel de tareas abierto a la derecha del formulario de composición.
En el panel de tareas, seleccione el gist Hello World Html e insertar para insertar el gist en el cuerpo del mensaje.
Pasos siguientes
En este tutorial, ha creado un complemento de Outlook que puede usarse en modo de composición de mensajes para insertar contenido en el cuerpo de un mensaje. Para obtener más información sobre el desarrollo de complementos de Outlook, pase al siguiente artículo.
Ejemplos de código
- Tutorial del complemento de Outlook completado: el resultado de completar este tutorial.