Search code examples
javascriptgoogle-apps-scriptgoogle-docs

Applying google app script code in selection in google docs


I have the following code that puts bold style some keywords in a whole google document:

function boldKeywords() {
  // Words that will be put in bold:
  var keywords = ["end", "proc", "fun"];

  var document = DocumentApp.getActiveDocument();
  var body = document.getBody();

  var Style = {};
  Style[DocumentApp.Attribute.BOLD] = true;

  for (j in keywords) {

    var found = body.findText(keywords[j]);

    while(found != null) {
      var foundText = found.getElement().asText();
      var start = found.getStartOffset();
      var end = found.getEndOffsetInclusive();
      foundText.setAttributes(start, end, Style)
      found = body.findText(keywords[j], found);
    }
  }

}

But I would like the code to put the keywords in bold only in the selected area of the document, for doing that, I tried using the function getSelection(), but I have the problem that this function returns a Range, but for applying findText I need a Body, somebody knows what could I do?


Solution

  • Modified Script

    function boldKeywordsInSelection() {
      const keywords = ["end", "proc", "fun"];
      const document = DocumentApp.getActiveDocument();
      const selection = document.getSelection();
    
       // get a list of all the different range elements
      const rangeElements = selection.getRangeElements();
    
      const Style = {};
      Style[DocumentApp.Attribute.BOLD] = true;
    
      // forEach used here because for in was giving me trouble...
      rangeElements.forEach(rangeElement => {
    
        // Each range element has a corresponding element (e.g. paragraph)
        const parentElement = rangeElement.getElement();
    
        // fixing the limits of the bold operations depending on the selection
        const startLimit = rangeElement.getStartOffset();
        const endLimit = rangeElement.getEndOffsetInclusive();
    
        for (j in keywords) {
          let found = parentElement.findText(keywords[j]);
          // wrapping in try catch to escape the for loop from within the while loop
          try {
            while (found != null) {
              const foundText = found.getElement().asText();
              const start = found.getStartOffset();
              // Checking if the start of the word is after the start of the selection
              if (start < startLimit) {
                // If so, then skip to next word
                found = parentElement.findText(keywords[j], found);
                continue;
              }
              // Checking if the start of the word is after the end of the selection
              // if so, go to next element
              if (start > endLimit) throw "out of selection";
              const end = found.getEndOffsetInclusive();
    
              foundText.setAttributes(start, end, Style)
              found = parentElement.findText(keywords[j], found);
            }
          } catch (e) {
            Logger.log(e)
            continue;
          }
        }
      })
    }
    

    See the comments in the code for more details.

    A getSelection produces a Range object, which contains within it various instances of RangeElement. Each RangeElement makes reference to a parent element, and the index positions within that parent. The parent is the element that the range element is a part of. For example:

    enter image description here

    This selection spans 3 elements. Therefore the selection has 3 range elements. You can only use the findText method on the whole element, not the range element.

    This means that the flow of the script is generally the same, except that you need to go through each element and find the text within each element. Since this will return elements that are outside the selection, you need to keep track of the index positions of the selection and the found element and make sure the found element is within the selection.

    References