Google Docs — популярный редактор документов для создания и редактирования текстовых документов, электронных таблиц и презентаций. С помощью Google Apps Script, языка сценариев для автоматизации задач в Google Apps, пользователи могут расширить свои возможности редактирования документов, добавив пользовательские функции в Документы Google. Одной из таких функций является возможность проверки URL-адресов в документе Google.

Документы Google позволяют пользователям вставлять гиперссылки в свои документы, что может быть полезно для предоставления дополнительных ресурсов и информации. Однако с увеличением количества вредоносных веб-сайтов и неработающих ссылок важно проверить действительность этих ссылок, прежде чем делиться документом. Виджет для проверки URL-адресов в Google Docs может сделать этот процесс более эффективным и удобным.

Следующие шаги помогут вам создать виджет для проверки URL-адресов в Документах Google с помощью скрипта Google Apps.

Скрипт приложений

Откройте документ Google на Google Диске и щелкните меню «Расширения». В раскрывающемся меню выберите «Скрипт приложений».

В Apps Script скопируйте и вставьте следующие части кода:

  1. Добавить часть кода для создания пункта меню
/* Code to add submenu item */
function onOpen(e) {
  DocumentApp.getUi().createAddonMenu()
    .addItem('Show widget', 'showSidebar')
    .addToUi();
}

/* Google script lifecycle */
function onInstall(e) {
  onOpen(e);
}

Это приведет к чему-то вроде этого:

2.Функция Expose для отображения HTML-виджета на боковой панели

/* Code to add submenu item */
function showSidebar() {
  const ui = HtmlService.createHtmlOutputFromFile('sidebar')
    .setTitle('Link Checker');
  DocumentApp.getUi().showSidebar(ui);
}

3. Код для анализа ссылок текущего открытого Документа Google

Большое спасибо mogsdad за предоставленную суть: https://gist.github.com/mogsdad/6518632

Включив это в нашу логику

function getAllLinks(element) {
  var links = [];
  element = element || DocumentApp.getActiveDocument().getBody();

  if (element.getType() === DocumentApp.ElementType.TEXT) {
    var textObj = element.editAsText();
    var text = element.getText();
    var inUrl = false;
    for (var ch = 0; ch < text.length; ch++) {
      var url = textObj.getLinkUrl(ch);
      if (url != null && ch != text.length - 1) {
        if (!inUrl) {
          // We are now!
          inUrl = true;
          var curUrl = {};
          curUrl.element = element;
          curUrl.url = String(url); // grab a copy
          curUrl.startOffset = ch;
        }
        else {
          curUrl.endOffsetInclusive = ch;
        }
      }
      else {
        if (inUrl) {
          // Not any more, we're not.
          inUrl = false;
          links.push(curUrl);  // add to links
          curUrl = {};
        }
      }
    }
  }
  else {
    // Get number of child elements, for elements that can have child elements. 
    try {
      var numChildren = element.getNumChildren();
    }
    catch (e) {
      numChildren = 0;
    }
    for (var i = 0; i < numChildren; i++) {
      links = links.concat(getAllLinks(element.getChild(i)));
    }
  }

  return links;
}

4. Создайте функцию для вызова основного кода

Мы будем вызывать эту функцию из пользовательского интерфейса для запуска агрегации ссылок.

function obtainLinks() {
  const links = getAllLinks();

  return links || [];
}

5. Код для проверки статуса ссылок

Также будет вызываться из пользовательского интерфейса

async function checkLinks(links) {
  const requests = UrlFetchApp.fetchAll(links.map(({ url }) => ({ url, method: 'get', followRedirects: false })))
  const results = requests.map(r => r.getResponseCode());

  return links.map((link, index) => ({url: link.url, code: results[index] || 0}));
}

6. Добавьте интерфейс

Не буду так подробно вдаваться:

  • Создайте sidebar.html в редакторе проектов Apps Script.
  • добавьте код ниже, чтобы указать и выполнить открытые функции
<!DOCTYPE html>
<html>

<head>
  <base target="_top">
  <title></title>
</head>

<body>
  <div class="sidebar branding-below">
    <form>
      <div class="block col-contain">

        <table class="links">
          <thead>
            <tr class="links_row">
              <th class="links_heading links_cell--url">URL</th>
              <th class="links_heading links_cell--status">Status</th>
            </tr>
          </thead>
          <tbody id="links" class="links_body"></tbody>
        </table>
      </div>
      <div class="block" id="button-bar">
        <button class="blue" id="obtain-links">Obtain links</button>
        <button class="green" id="check-links" disabled="disabled">Check links</button>
      </div>
    </form>
  </div>

  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  <script>
    /**
   * On document load, assign click handlers to each button and try to load the
   * user's origin and destination language preferences if previously set.
   */
  let currentLinks = [];
  $(function() {
    $('#obtain-links').click(obtainLinks);
    $('#check-links').click(checkLinks);
  });
  
  /**
   * Runs a server-side function to translate the user-selected text and update
   * the sidebar UI with the resulting translation.
   */
  function obtainLinks() {
    setBlueState(true);
    $('#error').remove();
    
    google.script.run
            .withSuccessHandler(
                    function(links, element) {
                      $('#links > *').remove();

                      currentLinks = links || [];
                      links.map(function(link) {
                        $('#links')
                        .append(`<tr class="links_row"><td class="links_cell links_cell--url">${link.url}</td><td class="links_cell links_cell--status">?</td></tr>`);
                      });
                      setGreenState();
                      setBlueState(false);
                    })
            .withFailureHandler(
                    function(msg, element) {
                      showError(msg, $('#button-bar'));
                      setBlueState(false);
                    })
            .withUserObject(this)
            .obtainLinks();
  }

  function setBlueState(disabled) {
    if(!disabled){
      $('#obtain-links').removeAttr('disabled');
    } else {
      $('#obtain-links').attr('disabled', 'disabled');
    }
  }

  function setGreenState() {
    if(currentLinks.length > 0){
      $('#check-links').removeAttr('disabled');
    } else {
      $('#check-links').attr('disabled', 'disabled');
    }
    
  }

  function checkLinks() {
    setBlueState(true);
    $('#error').remove();
    
    google.script.run
            .withSuccessHandler(
                    function(links, element) {
                      $('#links > *').remove();

                      currentLinks = links || [];
                      links.map(function(link) {
                        $('#links')
                        .append(`<tr class="links_row"><td class="links_cell links_cell--url">${link.url}</td><td class="links_cell links_cell--status">${link.code}</td></tr>`);
                      });
                      setGreenState();
                      setBlueState(false);
                    })
            .withFailureHandler(
                    function(msg, element) {
                      showError(msg, $('#button-bar'));
                      setBlueState(false);
                    })
            .withUserObject(this)
            .checkLinks(currentLinks);
  }

  function showError(msg, element) {
    const div = $('<div id="error" class="error">' + msg + '</div>');
    $(element).after(div);
  }
  </script>
</body>

</html>

Демонстрационное время

И в волшебстве некоторого дополнительного CSS у вас есть виджет:

  • Получает ссылки
  • Получает статусы