google-apps-scriptgoogle-docsgoogle-apps-script-addon

Google (Docs) Apps Script - Can't check if cursor on named range


I am inserting text into a document and each text insertion is added to a named range so that I can look them all up with getNamedRanges(NAME) and getNamedRangesById(ID).

Now I need to check if the current cursor position is on a named range and I have yet to figure out how.

This post is similar: How to determine the named range from position in Google Docs through Google Apps Script

But when the cursor is on a namedrange cursor.getElement() returns Text object, not the named range.

How can I determine if the cursor is currently positioned on a named range?


Solution

  • Workaround:

    In this workaround, I checked whether the cursor position is included in the namedRange by comparing the indexes of paragraph of both the namedRange and the cursor position.

    Flow:

    The flow of the script is as follows.

    1. Retrieve the indexes of paragraph of the namedRange.
      • I this sample script, from your question, the namedRange ID is used.
      • In this case, there might be multiple paragraphs including table, list and so on. So all indexes in the namedRange are retrieved.
    2. Retrieve the index of paragraph of the cursor position.
    3. Retrieve the index of paragraph of the selected range.
      • This sample script also checks whether the selected range is in the namedRange. Because when the text is selected, cursor becomes null.
    4. If the cursor or selected range are staying in the namedRange, myFunction() returns true.
      • If the cursor or selected range are not staying in the namedRange, myFunction() returns false.
      • Also you can confirm it at the log.

    Sample script:

    Before you use this script, please set the namedRange ID.

    function myFunction() {
      var nameRangeId = "###"; // Please set namedRange ID here.
      
      var getIndex = function(doc, e) {
        while (e.getParent().getType() != DocumentApp.ElementType.BODY_SECTION) e = e.getParent();
        return doc.getBody().getChildIndex(e);
      };
      var doc = DocumentApp.getActiveDocument();
      
      // For namedRange
      var namedRange = doc.getNamedRangeById(nameRangeId);
      if (namedRange) {
        var indexOfNamedRange = namedRange.getRange().getRangeElements().map(function(e) {return getIndex(doc, e.getElement())});
      } else {
        throw new Error("No namedRange.");
      }
      
      var name = namedRange.getName();
      
      // For cursor
      var cursor = doc.getCursor();
      if (cursor) {
        var indexOfCursor = getIndex(doc, cursor.getElement());
        if (~indexOfNamedRange.indexOf(indexOfCursor)) {
          Logger.log("Inside of %s", name);
          return true;
        }
        Logger.log("Outside of %s", name);
        return false;
      }
    
      // For select
      var select = doc.getSelection();
      if (select) {
        var indexOfSelect = select.getRangeElements().map(function(e) {return getIndex(doc, e.getElement())});
        if (indexOfSelect.some(function(e) {return ~indexOfNamedRange.indexOf(e)})) {
          Logger.log("Inside of %s", name);
          return true;
        }
        Logger.log("Outside of %s", name);
        return false;
      }
    
      throw new Error("No cursor and select.");
    }
    

    Note:

    References:

    Added:

    I had understood that from this situation, OP has set the named range to the paragraph. When I proposed a sample script for this, I thought that I correctly understood OP's goal. But, from gaspar's following comment,

    this only shows whether the cursor is in the same element as the named range, but in case of named range partial text it gives a false positive finding if the cursor is in the same element but not in the same text part

    If OP sets the part of the paragraph as the named range, and OP wants to check whether the cursor is included in the named range, the sample script is as follows.

    Sample script:

    function myFunction() {
      var nameRangeId = "###"; // Please set namedRange ID here.
    
      var getIndex = function (doc, e) {
        while (e.getParent().getType() != DocumentApp.ElementType.BODY_SECTION) e = e.getParent();
        return doc.getBody().getChildIndex(e);
      };
      var doc = DocumentApp.getActiveDocument();
    
      // For namedRange
      var namedRange = doc.getNamedRangeById(nameRangeId);
    
      if (namedRange) {
        var indexOfNamedRange = namedRange.getRange().getRangeElements().map(e => ({ idx: getIndex(doc, e.getElement()), start: e.getStartOffset(), end: e.getEndOffsetInclusive() }));
      } else {
        throw new Error("No namedRange.");
      }
      var name = namedRange.getName();
    
      // For cursor
      var cursor = doc.getCursor();
      if (cursor) {
        var indexOfCursor = getIndex(doc, cursor.getElement());
        var offset = cursor.getOffset();
        if (indexOfNamedRange.some(({ idx, start, end }) => idx == indexOfCursor && ((start == -1 && end == -1) || (offset > start && offset < end)))) {
          Logger.log("Inside of %s", name);
          return true;
        }
        Logger.log("Outside of %s", name);
        return false;
      }
    
      // For select
      var select = doc.getSelection();
      if (select) {
        var indexOfSelect = select.getRangeElements().map(e => ({ idx: getIndex(doc, e.getElement()), start: e.getStartOffset(), end: e.getEndOffsetInclusive() }));
        if (indexOfSelect.some(e => indexOfNamedRange.some(({ idx, start, end }) => idx == e.idx && ((start == -1 && end == -1) || ((e.start > start && e.start < end) || (e.end > start && e.end < end)))))) {
          Logger.log("Inside of %s", name);
          return true;
        }
        Logger.log("Outside of %s", name);
        return false;
      }
    
      throw new Error("No cursor and select.");
    }