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, 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.

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.

  1. Inicie sesión en GitHub.

  2. Cree un nuevo gist.

    • 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.

  3. Cree otro gist nuevo.

    • 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

  1. 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.

    • 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

    Las preguntas y respuestas del generador Yeoman en una interfaz de línea de comandos.

    Después de completar el asistente, el generador creará el proyecto e instalará componentes auxiliares de Node.

    Nota:

    Si usa Node.js versión 20.0.0 o posterior, es posible que vea una advertencia cuando el generador ejecute la instalación de que tiene un motor no admitido. Estamos trabajando en una solución para esto. Mientras tanto, la advertencia no afecta al generador ni al proyecto que se genera, por lo que se puede omitir.

    Sugerencia

    Se pueden pasar por alto las instrucciones de los pasos siguientes que el generador de Yeoman ofrece después de que se haya creado el proyecto de complemento. Las instrucciones paso a paso de este artículo le dan toda la información que necesitará para completar este tutorial.

  2. Vaya a la carpeta raíz del proyecto.

    cd "Git the gist"
    
  3. 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
    
  4. 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 comando code 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 manifest.xml para especificar cierta información básica sobre el complemento.

  1. Busque el <elemento ProviderName> y reemplace el valor predeterminado por el nombre de la empresa.

    <ProviderName>Contoso</ProviderName>
    
  2. Busque el <elemento Description> , reemplace el valor predeterminado por una descripción del complemento y guarde el archivo.

    <Description DefaultValue="Allows users to access their GitHub gists."/>
    

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.

  1. Ejecute el siguiente comando en el directorio principal 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.

  2. En Outlook, abra un mensaje existente y seleccione el botón Mostrar panel de tareas.

  3. 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.

    El botón Mostrar panel de tareas y git el panel de tareas gist agregado por el ejemplo.

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

Eliminar punto de extensión MessageReadCommandSurface

  1. Abra el archivo manifest.xml.

  2. Busque el <elemento ExtensionPoint> con el tipo MessageReadCommandSurface y elimínelo (incluida su etiqueta de cierre). Esto quita los botones del complemento de la ventana de mensajes de lectura.

Agregar el punto de extensión MessageComposeCommandSurface

  1. En manifest.xml, busque la línea en el manifiesto que lee </DesktopFormFactor>.

  2. Justo antes de esta línea, inserte el marcado XML siguiente. Tenga en cuenta lo siguiente sobre este marcado.

    • ExtensionPoint<> con xsi:type="MessageComposeCommandSurface" indica que va a definir los botones que se van a agregar a la ventana del mensaje de redacción.

    • Mediante el uso de un <elemento OfficeTab> con id="TabDefault", indica que desea agregar los botones a la pestaña predeterminada de la cinta de opciones.

    • El <elemento Group> define la agrupación de los nuevos botones, con una etiqueta establecida por el recurso groupLabel .

    • El primer <elemento Control> contiene un <elemento Action> con xsi:type="ShowTaskPane", por lo que este botón abre un panel de tareas.

    • El segundo <elemento Control> contiene un <elemento Action> con xsi:type="ExecuteFunction", por lo que este botón invoca una función de JavaScript contenida en el archivo de función.

    <!-- Message Compose -->
    <ExtensionPoint xsi:type="MessageComposeCommandSurface">
      <OfficeTab id="TabDefault">
        <Group id="msgComposeCmdGroup">
          <Label resid="GroupLabel"/>
          <Control xsi:type="Button" id="msgComposeInsertGist">
            <Label resid="TaskpaneButton.Label"/>
            <Supertip>
              <Title resid="TaskpaneButton.Title"/>
              <Description resid="TaskpaneButton.Tooltip"/>
            </Supertip>
            <Icon>
              <bt:Image size="16" resid="Icon.16x16"/>
              <bt:Image size="32" resid="Icon.32x32"/>
              <bt:Image size="80" resid="Icon.80x80"/>
            </Icon>
            <Action xsi:type="ShowTaskpane">
              <SourceLocation resid="Taskpane.Url"/>
            </Action>
          </Control>
          <Control xsi:type="Button" id="msgComposeInsertDefaultGist">
            <Label resid="FunctionButton.Label"/>
            <Supertip>
              <Title resid="FunctionButton.Title"/>
              <Description resid="FunctionButton.Tooltip"/>
            </Supertip>
            <Icon>
              <bt:Image size="16" resid="Icon.16x16"/>
              <bt:Image size="32" resid="Icon.32x32"/>
              <bt:Image size="80" resid="Icon.80x80"/>
            </Icon>
            <Action xsi:type="ExecuteFunction">
              <FunctionName>insertDefaultGist</FunctionName>
            </Action>
          </Control>
        </Group>
      </OfficeTab>
    </ExtensionPoint>
    

Actualizar recursos en el manifiesto

El código anterior hace referencia a etiquetas, información sobre herramientas y direcciones URL que debe definir para que el manifiesto sea válido. Especificará esta información en la <sección Recursos> del manifiesto.

  1. En manifest.xml, busque el <elemento Resources> en el archivo de manifiesto y elimine todo el elemento (incluida su etiqueta de cierre).

  2. En esa misma ubicación, agregue el marcado siguiente para reemplazar el <elemento Resources> que acaba de quitar.

    <Resources>
      <bt:Images>
        <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/>
        <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/>
        <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/>
      </bt:Images>
      <bt:Urls>
        <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html"/>
        <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html"/>
      </bt:Urls>
      <bt:ShortStrings>
        <bt:String id="GroupLabel" DefaultValue="Git the gist"/>
        <bt:String id="TaskpaneButton.Label" DefaultValue="Insert gist"/>
        <bt:String id="TaskpaneButton.Title" DefaultValue="Insert gist"/>
        <bt:String id="FunctionButton.Label" DefaultValue="Insert default gist"/>
        <bt:String id="FunctionButton.Title" DefaultValue="Insert default gist"/>
      </bt:ShortStrings>
      <bt:LongStrings>
        <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Displays a list of your gists and allows you to insert their contents into the current message."/>
        <bt:String id="FunctionButton.Tooltip" DefaultValue="Inserts the content of the gist you mark as default into the current message."/>
      </bt:LongStrings>
    </Resources>
    
  3. Guarde los cambios en el manifiesto.

Reinstalar el complemento

Debe reinstalar el complemento para que los cambios del manifiesto surtan efecto.

  1. Si el servidor web se está ejecutando, ejecute el siguiente comando.

    npm stop
    
  2. 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.

    Menú de desbordamiento de la cinta de opciones en Outlook en Windows con los botones del complemento resaltados.

  • Si ejecuta este complemento en Outlook en la Web o nuevo Outlook en Windows (versión preliminar), 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.

    Formulario de redacción de mensajes en Outlook en la Web con el botón de complemento y el menú emergente resaltados.

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.

  1. En la carpeta ./src, cree una subcarpeta denominada configuración.

  2. En la carpeta ./src/settings , cree un archivo denominado dialog.html.

  3. 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://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/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.

  4. Guarde los cambios.

  5. A continuación, cree un archivo en la carpeta ./src/settings denominada dialog.css.

  6. 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;
    }
    
  7. 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.

  1. En la carpeta ./src/settings , cree un archivo denominado dialog.js.

  2. 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, " "));
      }
    })();
    
  3. 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.

  1. Busque el objeto entry en el objeto config y agregue una nueva entrada para dialog.

    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",
    },
    
  2. Busque la matriz plugins en el objeto config. En la matriz patterns del objeto new 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 new CopyWebpackPlugin tendrá el siguiente aspecto:

    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*.xml",
        to: "[name]" + "[ext]",
        transform(content) {
          if (dev) {
            return content;
          } else {
            return content.toString().replace(new RegExp(urlDev, "g"), urlProd);
          }
        },
      },
    ]}),
    
  3. En la misma matriz plugins dentro del objeto config, 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 matriz plugins tendrá el siguiente aspecto:

    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*.xml",
            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.

  1. En la carpeta ./src, cree una subcarpeta denominada auxiliares.

  2. En la carpeta ./src/helpers , cree un archivo denominado gist-api.js.

  3. 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;
    }
    
  4. Guarde los cambios.

  5. 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.

  1. 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.

  2. Guarde los cambios.

Actualizar el archivo de la función (JavaScript)

  1. 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;
    }
    
  2. Guarde los cambios.

Crear un archivo para administrar la configuración

  1. 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);
    }
    
  2. Guarde los cambios.

Crear funciones nuevas para procesar los gist

  1. 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');
    }
    
  2. Guarde los cambios.

Probar el botón Insertar gist predeterminado

  1. Si el servidor web local aún no se está ejecutando, ejecute npm start desde el símbolo del sistema.

  2. Abra Outlook y redacte un mensaje nuevo.

  3. 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.

    El símbolo del sistema del cuadro de diálogo para configurar el complemento.

  4. 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.

    Cuadro de diálogo de configuración del complemento.

  5. 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

  1. 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://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/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>
    
  2. Guarde los cambios.

Especifique el CSS para el panel de tareas

  1. 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; }
    
  2. Guarde los cambios.

Especifique el JavaScript para el panel de tareas

  1. 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;
      }
    })();
    
  2. Guarde los cambios.

Probar el botón Insertar gist

  1. Si el servidor web local aún no se está ejecutando, ejecute npm start desde el símbolo del sistema.

  2. Abra Outlook y redacte un mensaje nuevo.

  3. 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.

  4. En el panel de tareas, seleccione el gist Hello World Html e insertar para insertar el gist en el cuerpo del mensaje.

El panel de tareas del complemento y el contenido gist seleccionado que se muestra 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

Vea también