透過智慧型方塊預覽連結

本頁說明如何建構 Google Workspace 外掛程式,讓 Google 文件、試算表和簡報使用者預覽第三方服務的連結。

Google Workspace 外掛程式可以偵測服務的連結,並提示使用者預覽連結內容。您可以設定外掛程式,預覽多個網址模式,例如支援案件、銷售線索和員工設定檔的連結。

使用者如何預覽連結

如要預覽連結,使用者可以與智慧型方塊資訊卡互動。

使用者預覽資訊卡

使用者在文件或試算表中輸入或貼上網址時,Google 文件或 Google 試算表會提示他們將連結替換為智慧型方塊。智慧型方塊會顯示圖示,以及連結內容的簡短名稱或說明。使用者將游標懸停在方塊上時,會看到資訊卡介面,其中預覽了檔案或連結的詳細資訊。

以下影片顯示使用者如何將連結轉換為智慧型方塊,並預覽資訊卡:

使用者如何預覽 Google 簡報中的連結

Google 簡報不支援第三方智慧型方塊的連結預覽功能。使用者在簡報中輸入或貼上網址時,Google 簡報會提示他們將連結替換為連結文字,而非方塊。當使用者將游標懸停在連結標題上時,系統會顯示資訊卡介面,預覽連結相關資訊。

下圖顯示連結預覽在 Google 簡報中的轉譯方式:

Google 簡報的連結預覽範例

必要條件

Apps Script

Node.js

Python

Java

選用:設定第三方服務的驗證

如果外掛程式連線的服務需要授權,使用者必須驗證服務,才能預覽連結。也就是說,使用者首次將服務中的連結貼到文件、試算表或簡報檔案時,外掛程式必須叫用授權流程。

如要設定 OAuth 服務或自訂授權提示,請參閱「將外掛程式連結至第三方服務」。

本節說明如何為外掛程式設定連結預覽畫面,包括下列步驟:

  1. 在外掛程式資訊清單中設定連結預覽
  2. 為連結建立智慧型方塊和資訊卡介面

設定連結預覽

如要設定連結預覽,請在外掛程式的資訊清單中指定下列區段和欄位:

  1. addOns 區段下方,新增 docs 欄位來擴充 Google 文件、新增 sheets 欄位來擴充 Google 試算表,以及新增 slides 欄位來擴充 Google 簡報。
  2. 在每個欄位中,實作包含 runFunctionlinkPreviewTriggers 觸發條件 (您會在下一個「建構智慧型方塊和資訊卡」一節中定義這個函式)。

    如要瞭解可在 linkPreviewTriggers 觸發條件中指定的欄位,請參閱 Apps Script 資訊清單其他執行階段的部署資源參考文件。

  3. oauthScopes 欄位中新增範圍 https://www.googleapis.com/auth/workspace.linkpreview,讓使用者授權外掛程式代表他們預覽連結。

舉例來說,請參閱下列資訊清單的 oauthScopesaddons 區段,瞭解如何為客服案件服務設定連結預覽。

{
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview"
  ],
  "addOns": {
    "common": {
      "name": "Preview support cases",
      "logoUrl": "https://www.example.com/images/company-logo.png",
      "layoutProperties": {
        "primaryColor": "#dd4b39"
      }
    },
    "docs": {
      "linkPreviewTriggers": [
        {
          "runFunction": "caseLinkPreview",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "support/cases"
            },
            {
              "hostPattern": "*.example.com",
              "pathPrefix": "cases"
            },
            {
              "hostPattern": "cases.example.com"
            }
          ],
          "labelText": "Support case",
          "logoUrl": "https://www.example.com/images/support-icon.png",
          "localizedLabelText": {
            "es": "Caso de soporte"
          }
        }
      ]
    },
    "sheets": {
      "linkPreviewTriggers": [
        {
          "runFunction": "caseLinkPreview",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "support/cases"
            },
            {
              "hostPattern": "*.example.com",
              "pathPrefix": "cases"
            },
            {
              "hostPattern": "cases.example.com"
            }
          ],
          "labelText": "Support case",
          "logoUrl": "https://www.example.com/images/support-icon.png",
          "localizedLabelText": {
            "es": "Caso de soporte"
          }
        }
      ]
    },
    "slides": {
      "linkPreviewTriggers": [
        {
          "runFunction": "caseLinkPreview",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "support/cases"
            },
            {
              "hostPattern": "*.example.com",
              "pathPrefix": "cases"
            },
            {
              "hostPattern": "cases.example.com"
            }
          ],
          "labelText": "Support case",
          "logoUrl": "https://www.example.com/images/support-icon.png",
          "localizedLabelText": {
            "es": "Caso de soporte"
          }
        }
      ]
    }
  }
}

在這個範例中,Google Workspace 外掛程式會預覽公司客服案件的連結。外掛程式會指定三個網址模式來預覽連結。每當連結符合其中一個網址模式時,回呼函式 caseLinkPreview 會在 Google 文件、試算表或簡報中建立並顯示資訊卡和智慧型方塊,並將網址替換為連結標題。

建立智慧型方塊和資訊卡

如要傳回連結的智慧型方塊和資訊卡,您必須實作在 linkPreviewTriggers 物件中指定的任何函式。

當使用者與符合指定網址模式的連結互動時,系統會觸發 linkPreviewTriggers,而其回呼函式會將事件物件 EDITOR_NAME.matchedUrl.url 做為引數傳遞。您可以使用這個事件物件的酬載,為連結預覽畫面建構智慧型方塊和資訊卡。

舉例來說,如果使用者在 Google 文件中預覽連結 https://www.example.com/cases/123456,系統會傳回下列事件酬載:

JSON

{
  "docs": {
    "matchedUrl": {
        "url": "https://www.example.com/support/cases/123456"
    }
  }
}

如要建立資訊卡介面,請使用小工具顯示連結的相關資訊。您也可以建立動作,讓使用者開啟連結或修改連結內容。如要查看可用的小工具和動作清單,請參閱「支援的預覽資訊卡元件」。

如要為連結預覽畫面建構智慧型方塊和資訊卡,請按照下列步驟操作:

  1. 實作您在外掛程式資訊清單的 linkPreviewTriggers 區段中指定的函式:
    1. 函式必須接受包含 EDITOR_NAME.matchedUrl.url 的事件物件做為引數,並傳回單一 Card 物件。
    2. 如果服務需要授權,函式也必須叫用授權流程
  2. 針對每個預覽資訊卡,實作任何可為介面提供小工具互動性的回呼函式。舉例來說,如果您加入「查看連結」按鈕,可以建立動作,指定回呼函式在新視窗中開啟連結。如要進一步瞭解小工具互動,請參閱「外掛程式動作」。

下列程式碼會為文件建立回呼函式 caseLinkPreview

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
* Entry point for a support case link preview.
*
* @param {!Object} event The event object.
* @return {!Card} The resulting preview link card.
*/
function caseLinkPreview(event) {

  // If the event object URL matches a specified pattern for support case links.
  if (event.docs.matchedUrl.url) {

    // Uses the event object to parse the URL and identify the case details.
    const caseDetails = parseQuery(event.docs.matchedUrl.url);

    // Builds a preview card with the case name, and description
    const caseHeader = CardService.newCardHeader()
      .setTitle(`Case ${caseDetails["name"][0]}`);
    const caseDescription = CardService.newTextParagraph()
      .setText(caseDetails["description"][0]);

    // Returns the card.
    // Uses the text from the card's header for the title of the smart chip.
    return CardService.newCardBuilder()
      .setHeader(caseHeader)
      .addSection(CardService.newCardSection().addWidget(caseDescription))
      .build();
  }
}

/**
* Extracts the URL parameters from the given URL.
*
* @param {!string} url The URL to parse.
* @return {!Map} A map with the extracted URL parameters.
*/
function parseQuery(url) {
  const query = url.split("?")[1];
  if (query) {
    return query.split("&")
    .reduce(function(o, e) {
      var temp = e.split("=");
      var key = temp[0].trim();
      var value = temp[1].trim();
      value = isNaN(value) ? value : Number(value);
      if (o[key]) {
        o[key].push(value);
      } else {
        o[key] = [value];
      }
      return o;
    }, {});
  }
  return null;
}

Node.js

node/3p-resources/index.js
/**
 * 
 * A support case link preview.
 *
 * @param {!URL} url The event object.
 * @return {!Card} The resulting preview link card.
 */
function caseLinkPreview(url) {
  // Builds a preview card with the case name, and description
  // Uses the text from the card's header for the title of the smart chip.
  // Parses the URL and identify the case details.
  const name = `Case ${url.searchParams.get("name")}`;
  return {
    action: {
      linkPreview: {
        title: name,
        previewCard: {
          header: {
            title: name
          },
          sections: [{
            widgets: [{
              textParagraph: {
                text: url.searchParams.get("description")
              }
            }]
          }]
        }
      }
    }
  };
}

Python

python/3p-resources/create_link_preview/main.py
def case_link_preview(url):
    """A support case link preview.
    Args:
      url: A matching URL.
    Returns:
      The resulting preview link card.
    """

    # Parses the URL and identify the case details.
    query_string = parse_qs(url.query)
    name = f'Case {query_string["name"][0]}'
    # Uses the text from the card's header for the title of the smart chip.
    return {
        "action": {
            "linkPreview": {
                "title": name,
                "previewCard": {
                    "header": {
                        "title": name
                    },
                    "sections": [{
                        "widgets": [{
                            "textParagraph": {
                                "text": query_string["description"][0]
                            }
                        }]
                    }],
                }
            }
        }
    }

Java

java/3p-resources/src/main/java/CreateLinkPreview.java
/**
 * A support case link preview.
 *
 * @param url A matching URL.
 * @return The resulting preview link card.
 */
JsonObject caseLinkPreview(URL url) throws UnsupportedEncodingException {
  // Parses the URL and identify the case details.
  Map<String, String> caseDetails = new HashMap<String, String>();
  for (String pair : url.getQuery().split("&")) {
      caseDetails.put(URLDecoder.decode(pair.split("=")[0], "UTF-8"), URLDecoder.decode(pair.split("=")[1], "UTF-8"));
  }

  // Builds a preview card with the case name, and description
  // Uses the text from the card's header for the title of the smart chip.
  JsonObject cardHeader = new JsonObject();
  String caseName = String.format("Case %s", caseDetails.get("name"));
  cardHeader.add("title", new JsonPrimitive(caseName));

  JsonObject textParagraph = new JsonObject();
  textParagraph.add("text", new JsonPrimitive(caseDetails.get("description")));

  JsonObject widget = new JsonObject();
  widget.add("textParagraph", textParagraph);

  JsonArray widgets = new JsonArray();
  widgets.add(widget);

  JsonObject section = new JsonObject();
  section.add("widgets", widgets);

  JsonArray sections = new JsonArray();
  sections.add(section);

  JsonObject previewCard = new JsonObject();
  previewCard.add("header", cardHeader);
  previewCard.add("sections", sections);

  JsonObject linkPreview = new JsonObject();
  linkPreview.add("title", new JsonPrimitive(caseName));
  linkPreview.add("previewCard", previewCard);

  JsonObject action = new JsonObject();
  action.add("linkPreview", linkPreview);

  JsonObject renderActions = new JsonObject();
  renderActions.add("action", action);

  return renderActions;
}

預覽資訊卡支援的元件

Google Workspace 外掛程式支援下列小工具和動作,可預覽連結卡片:

Apps Script

「卡片服務」欄位 類型
TextParagraph 小工具
DecoratedText 小工具
Image 小工具
IconImage 小工具
ButtonSet 小工具
TextButton 小工具
ImageButton 小工具
Grid 小工具
Divider 小工具
OpenLink 動作
Navigation 動作
僅支援 updateCard 方法。

JSON

資訊卡google.apps.card.v1欄位 類型
TextParagraph 小工具
DecoratedText 小工具
Image 小工具
Icon 小工具
ButtonList 小工具
Button 小工具
Grid 小工具
Divider 小工具
OpenLink 動作
Navigation 動作
僅支援 updateCard 方法。

完整範例:客服案件外掛程式

以下範例是 Google Workspace 外掛程式,可在 Google 文件中預覽公司支援案件的連結。

這個範例會執行下列操作:

  • 預覽客服案件的連結,例如 https://www.example.com/support/cases/1234。智慧型方塊會顯示支援圖示,預覽資訊卡則會包含案件 ID 和說明。
  • 如果使用者將語言代碼設為西班牙文,智慧型方塊會將 labelText 本地化為西班牙文。

資訊清單

Apps Script

apps-script/3p-resources/appsscript.json
{
  "timeZone": "America/New_York",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview",
    "https://www.googleapis.com/auth/workspace.linkcreate"
  ],
  "addOns": {
    "common": {
      "name": "Manage support cases",
      "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png",
      "layoutProperties": {
        "primaryColor": "#dd4b39"
      }
    },
    "docs": {
      "linkPreviewTriggers": [
        {
          "runFunction": "caseLinkPreview",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "support/cases"
            },
            {
              "hostPattern": "*.example.com",
              "pathPrefix": "cases"
            },
            {
              "hostPattern": "cases.example.com"
            }
          ],
          "labelText": "Support case",
          "localizedLabelText": {
            "es": "Caso de soporte"
          },
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png"
        }
      ],
      "createActionTriggers": [
        {
          "id": "createCase",
          "labelText": "Create support case",
          "localizedLabelText": {
            "es": "Crear caso de soporte"
          },
          "runFunction": "createCaseInputCard",
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png"
        }
      ]
    }
  }
}

JSON

{
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview"
  ],
  "addOns": {
    "common": {
      "name": "Preview support cases",
      "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png",
      "layoutProperties": {
        "primaryColor": "#dd4b39"
      }
    },
    "docs": {
      "linkPreviewTriggers": [
        {
          "runFunction": "URL",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "support/cases"
            },
            {
              "hostPattern": "*.example.com",
              "pathPrefix": "cases"
            },
            {
              "hostPattern": "cases.example.com"
            }
          ],
          "labelText": "Support case",
          "localizedLabelText": {
            "es": "Caso de soporte"
          },
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png"
        }
      ]
    }
  }
}

程式碼

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
* Entry point for a support case link preview.
*
* @param {!Object} event The event object.
* @return {!Card} The resulting preview link card.
*/
function caseLinkPreview(event) {

  // If the event object URL matches a specified pattern for support case links.
  if (event.docs.matchedUrl.url) {

    // Uses the event object to parse the URL and identify the case details.
    const caseDetails = parseQuery(event.docs.matchedUrl.url);

    // Builds a preview card with the case name, and description
    const caseHeader = CardService.newCardHeader()
      .setTitle(`Case ${caseDetails["name"][0]}`);
    const caseDescription = CardService.newTextParagraph()
      .setText(caseDetails["description"][0]);

    // Returns the card.
    // Uses the text from the card's header for the title of the smart chip.
    return CardService.newCardBuilder()
      .setHeader(caseHeader)
      .addSection(CardService.newCardSection().addWidget(caseDescription))
      .build();
  }
}

/**
* Extracts the URL parameters from the given URL.
*
* @param {!string} url The URL to parse.
* @return {!Map} A map with the extracted URL parameters.
*/
function parseQuery(url) {
  const query = url.split("?")[1];
  if (query) {
    return query.split("&")
    .reduce(function(o, e) {
      var temp = e.split("=");
      var key = temp[0].trim();
      var value = temp[1].trim();
      value = isNaN(value) ? value : Number(value);
      if (o[key]) {
        o[key].push(value);
      } else {
        o[key] = [value];
      }
      return o;
    }, {});
  }
  return null;
}

Node.js

node/3p-resources/index.js
/**
 * Responds to any HTTP request related to link previews.
 *
 * @param {Object} req An HTTP request context.
 * @param {Object} res An HTTP response context.
 */
exports.createLinkPreview = (req, res) => {
  const event = req.body;
  if (event.docs.matchedUrl.url) {
    const url = event.docs.matchedUrl.url;
    const parsedUrl = new URL(url);
    // If the event object URL matches a specified pattern for preview links.
    if (parsedUrl.hostname === 'example.com') {
      if (parsedUrl.pathname.startsWith('/support/cases/')) {
        return res.json(caseLinkPreview(parsedUrl));
      }
    }
  }
};


/**
 * 
 * A support case link preview.
 *
 * @param {!URL} url The event object.
 * @return {!Card} The resulting preview link card.
 */
function caseLinkPreview(url) {
  // Builds a preview card with the case name, and description
  // Uses the text from the card's header for the title of the smart chip.
  // Parses the URL and identify the case details.
  const name = `Case ${url.searchParams.get("name")}`;
  return {
    action: {
      linkPreview: {
        title: name,
        previewCard: {
          header: {
            title: name
          },
          sections: [{
            widgets: [{
              textParagraph: {
                text: url.searchParams.get("description")
              }
            }]
          }]
        }
      }
    }
  };
}

Python

python/3p-resources/create_link_preview/main.py
from typing import Any, Mapping
from urllib.parse import urlparse, parse_qs

import flask
import functions_framework


@functions_framework.http
def create_link_preview(req: flask.Request):
    """Responds to any HTTP request related to link previews.
    Args:
      req: An HTTP request context.
    Returns:
      An HTTP response context.
    """
    event = req.get_json(silent=True)
    if event["docs"]["matchedUrl"]["url"]:
        url = event["docs"]["matchedUrl"]["url"]
        parsed_url = urlparse(url)
        # If the event object URL matches a specified pattern for preview links.
        if parsed_url.hostname == "example.com":
            if parsed_url.path.startswith("/support/cases/"):
                return case_link_preview(parsed_url)

    return {}




def case_link_preview(url):
    """A support case link preview.
    Args:
      url: A matching URL.
    Returns:
      The resulting preview link card.
    """

    # Parses the URL and identify the case details.
    query_string = parse_qs(url.query)
    name = f'Case {query_string["name"][0]}'
    # Uses the text from the card's header for the title of the smart chip.
    return {
        "action": {
            "linkPreview": {
                "title": name,
                "previewCard": {
                    "header": {
                        "title": name
                    },
                    "sections": [{
                        "widgets": [{
                            "textParagraph": {
                                "text": query_string["description"][0]
                            }
                        }]
                    }],
                }
            }
        }
    }

Java

java/3p-resources/src/main/java/CreateLinkPreview.java
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

public class CreateLinkPreview implements HttpFunction {
  private static final Gson gson = new Gson();

  /**
   * Responds to any HTTP request related to link previews.
   *
   * @param request An HTTP request context.
   * @param response An HTTP response context.
   */
  @Override
  public void service(HttpRequest request, HttpResponse response) throws Exception {
    JsonObject event = gson.fromJson(request.getReader(), JsonObject.class);
    String url = event.getAsJsonObject("docs")
        .getAsJsonObject("matchedUrl")
        .get("url")
        .getAsString();
    URL parsedURL = new URL(url);
    // If the event object URL matches a specified pattern for preview links.
    if ("example.com".equals(parsedURL.getHost())) {
      if (parsedURL.getPath().startsWith("/support/cases/")) {
        response.getWriter().write(gson.toJson(caseLinkPreview(parsedURL)));
        return;
      }
    }

    response.getWriter().write("{}");
  }


  /**
   * A support case link preview.
   *
   * @param url A matching URL.
   * @return The resulting preview link card.
   */
  JsonObject caseLinkPreview(URL url) throws UnsupportedEncodingException {
    // Parses the URL and identify the case details.
    Map<String, String> caseDetails = new HashMap<String, String>();
    for (String pair : url.getQuery().split("&")) {
        caseDetails.put(URLDecoder.decode(pair.split("=")[0], "UTF-8"), URLDecoder.decode(pair.split("=")[1], "UTF-8"));
    }

    // Builds a preview card with the case name, and description
    // Uses the text from the card's header for the title of the smart chip.
    JsonObject cardHeader = new JsonObject();
    String caseName = String.format("Case %s", caseDetails.get("name"));
    cardHeader.add("title", new JsonPrimitive(caseName));

    JsonObject textParagraph = new JsonObject();
    textParagraph.add("text", new JsonPrimitive(caseDetails.get("description")));

    JsonObject widget = new JsonObject();
    widget.add("textParagraph", textParagraph);

    JsonArray widgets = new JsonArray();
    widgets.add(widget);

    JsonObject section = new JsonObject();
    section.add("widgets", widgets);

    JsonArray sections = new JsonArray();
    sections.add(section);

    JsonObject previewCard = new JsonObject();
    previewCard.add("header", cardHeader);
    previewCard.add("sections", sections);

    JsonObject linkPreview = new JsonObject();
    linkPreview.add("title", new JsonPrimitive(caseName));
    linkPreview.add("previewCard", previewCard);

    JsonObject action = new JsonObject();
    action.add("linkPreview", linkPreview);

    JsonObject renderActions = new JsonObject();
    renderActions.add("action", action);

    return renderActions;
  }

}