Skip to content

Feature / Improvement: Refreshing a Thread after sending a reply #812

@Tcintra

Description

@Tcintra

For my use case, I inject a new "Send" button that replaces the original send button. It works well, but on replies/forwards I want to be able to refresh the thread with new messages (without a hard refresh).

The gmailJS.api.tools.rerender() function (almost) works for me. Unfortunately, the Gmail client doesn't immediately know that the draft was sent (and a new message received). I've extended the rerender() function to the following, and would love to get some feedback here (especially if there's a better way to do this).

The key change here is (a) increasing the timeout (I found that ~1500ms works 90% of the time with good WiFi), and (b) adding a retry that checks if the message count has increased following the soft navigation. If the message count hasn't increased, then retry.

If helpful, I'd be more than happy to contribute this change in a PR, although I imagine this is still too "hacky" in its current state to pass the sniff test. Have a similar function for if the user is in split view mode, which I can share as well.

Thanks!

async function rerenderThreadFullView() {
  const url = window.location.href;
  const hash = window.location.hash;

  const initialThreadData = getThreadData();

  if (!initialThreadData) {
    console.warn("Not in thread, should not rerender.");
    return;
  }

  const { messageCount: initialMessageCount } = initialThreadData;

  function replace() {
    let tempUrl;
    if (hash.indexOf("/") !== -1) {
      tempUrl = url.replace(/#.*?\//, "#/");
    } else {
      tempUrl = url.replace(/#.*/, "#");
    }
    window.location.replace(tempUrl);

    setTimeout(() => {
      window.location.replace(url);
    }, 1);
  }

  await pollForResult(
    async () => {
      replace();
      const threadData = getThreadData();
      if (!threadData) {
        return false;
      }

      const { messageCount, lastMessage } = threadData;

      if (messageCount > initialMessageCount) {
        lastMessage.scrollIntoView({ behavior: "smooth" });
        return true;
      }

      return false;
    },
    { maxAttempts: 3, delayMs: 1500 },
  );
}

function getThreadData(): {
  messageCount: number;
  lastMessage: HTMLElement;
} | null {
  const listEls = document.querySelectorAll<HTMLElement>('div[role="list"]');

  const firstListEl = listEls[0];

  if (!firstListEl) {
    console.warn("No list elements found.");
    return null;
  }

  if (listEls.length > 1) {
    console.warn("Multiple list elements found.");
    return null;
  }

  return {
    messageCount: firstListEl.childElementCount,
    lastMessage: firstListEl.lastElementChild as HTMLElement,
  };
}

export async function pollForResult<T>(
  fn: () => T | Promise<T>,
  opts: {
    maxAttempts: number;
    delayMs: number;
  },
) {
  let attempts = 0;
  const { maxAttempts, delayMs } = opts;

  while (attempts < maxAttempts) {
    const result = await fn();

    if (result) {
      return result;
    }

    await new Promise((resolve) => setTimeout(resolve, delayMs));

    attempts++;
  }

  return null;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions