Skip to content

[Feature Request] Option to disable scrolling to the bottom when a new message is sent in the chat #682

Description

@hankertrix

Is your feature request related to a problem? Please describe.
Currently, every single new message sent causes the chat window to scroll all the way to the bottom. This is not ideal for long conversations as you will always need to jump back to the message being replied to, either by pinning the message or clicking the reply message.

This gets old very quickly, and is rather annoying, so I would like an option to disable this behaviour.

Describe the solution you'd like
Just an option in the settings menu to disable automatic scrolling to the bottom when a new message is sent.

Describe alternatives you've considered
I have developed a userscript to block the client's automatic scrolling, but it doesn't work on longer conversations as the chat window's scrollTop is not stable, and different values can refer to different positions in the chat window.

I have pasted the userscript below:

(function () {
  "use strict";

  // The maximum delay in milliseconds
  // after the message is sent to block the scroll
  const MAX_DELAY_MS = 1000;

  // The keys that send the message
  const KEYS_TO_SEND_MESSAGE = ["Enter"];

  // The send buttons
  const SEND_BUTTONS = {
    webK: ".btn-send",
    webA: ".main-button",
  };

  // The input elements
  const INPUT_ELEMENTS = {
    webK: ".input-message-input.forwards",
    webA: "#editable-message-text",
  };

  // The chat window
  const CHAT_WINDOWS = {
    webK: ".bubbles-scrollable",
    webA: ".MessageList",
  };

  // The regular expression to remove the . and # from the selector
  const REMOVE_SELECTOR_REGEX = /^[.#]+|[.#]$/g;

  // The scrollTop property
  const SCROLL_TOP = "scrollTop";

  // Initialise the message sent time
  let messageSentTime = 0;

  // The event listener for the button
  function updatePreviousState() {

    // Set the message sent time
    messageSentTime = performance.now();
  }

  // Create an observer to watch for changes to the page
  const observer = new MutationObserver(() => {

    // Try to get the button to send the message
    const sendButton = document.querySelector(
      Object.values(SEND_BUTTONS).join(", "),
    );

    // Try to get the input element
    const input = document.querySelector(
      Object.values(INPUT_ELEMENTS).join(", "),
    );

    // If the button exists and
    // the event listener attribute doesn't exist
    if (sendButton && !sendButton.dataset.blockerAdded) {

      // Add the click event listener to it
      sendButton.addEventListener("click", updatePreviousState);

      // Set that the event listener has been added
      sendButton.dataset.blockerAdded = true;
    }

    // If the input element exists and
    // the event listener attribute doesn't exist
    if (input && !input.dataset.blockerAdded) {

      // Add the event listener to update the message sent time
      // when the keys used to send a message are pressed
      input.addEventListener("keydown", event => {
        if (KEYS_TO_SEND_MESSAGE.includes(event.code)) {
          updatePreviousState();
        }
      });

      // Set that the event listener has been added
      input.dataset.blockerAdded = true;
    }
  });

  // Get the prototype of the html element
  const prototype = HTMLElement.prototype;

  // Get the descriptor for the element
  const descriptor =
    Object.getOwnPropertyDescriptor(prototype, SCROLL_TOP) ||
    Object.getOwnPropertyDescriptor(Element.prototype, SCROLL_TOP);

  // Define the getter and setter of the scrollTop property
  Object.defineProperty(prototype, SCROLL_TOP, {

    // Define the getter
    get() {
      return descriptor.get.call(this);
    },

    // Define the setter
    set(value) {

      // Initialise whether the element is the chat window
      let isChatWindow = false;

      // Iterate over all the selectors for the chat window
      for (const selector of Object.values(CHAT_WINDOWS)) {

        // Get the class name
        const className = selector.replace(REMOVE_SELECTOR_REGEX, "");

        // If the class list contains the class name,
        // set that the element is the chat window
        if (this.classList.contains(className)) {
          isChatWindow = true;
        }
      }

      // If the element is not the chat window
      if (!isChatWindow) {

        // Call the original set method
        descriptor.set.call(this, value);

        // Exit the function
        return;
      }

      // console.log("Current value:", descriptor.get.call(this));
      // console.log("Given value:", value);

      // Get whether the send button has been pressed recently
      const sendButtonPressedRecently =
        performance.now() - messageSentTime < MAX_DELAY_MS;

      // If all the above conditions are met,
      // block the scroll by exiting the function
      if (sendButtonPressedRecently) {
        // console.warn("Blocked programmatic scroll");
        return;
      }

      // Use the original set call to set the value
      descriptor.set.call(this, value);
    },

    configurable: true,
    enumerable: descriptor.enumerable,
  });

  // Observe the document body
  observer.observe(document.body, {
    childList: true,
    subtree: true,
  });

  // console.log("Telegram scroll blocker installed");
})();

Additional context
I think this shouldn't be too difficult to implement, so I hope it can be implemented as it would make chatting much easier for me.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions