<commit_before>import React, { Component } from 'react';
import classnames from 'classnames';
import _ from 'lodash';
import moment from 'moment';

import { Route, Link } from "react-router-dom";
import { store } from "/store";

import { ResubscribeElement } from '/components/lib/resubscribe-element';
import { BacklogElement } from '/components/lib/backlog-element';
import { Message } from '/components/lib/message';
import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
import { ChatTabBar } from '/components/lib/chat-tabbar';
import { ChatInput } from '/components/lib/chat-input';
import { UnreadNotice } from '/components/lib/unread-notice';
import { deSig } from '/lib/util';

function getNumPending(props) {
  const result = props.pendingMessages.has(props.station)
    ? props.pendingMessages.get(props.station).length
    : 0;
  return result;
}

export class ChatScreen extends Component {
  constructor(props) {
    super(props);

    this.state = {
      numPages: 1,
      scrollLocked: false,
      // only for FF
      lastScrollHeight: null,
      scrollBottom: true
    };

    this.hasAskedForMessages = false;
    this.lastNumPending = 0;

    this.scrollContainer = null;
    this.onScroll = this.onScroll.bind(this);

    this.unreadMarker = null;

    moment.updateLocale('en', {
      calendar: {
        sameDay: '[Today]',
        nextDay: '[Tomorrow]',
        nextWeek: 'dddd',
        lastDay: '[Yesterday]',
        lastWeek: '[Last] dddd',
        sameElse: 'DD/MM/YYYY'
      }
    });

  }

  componentDidMount() {
    this.askForMessages();
    this.scrollToBottom();
  }


  componentDidUpdate(prevProps, prevState) {
    const { props, state } = this;

    if (
      prevProps.match.params.station !== props.match.params.station ||
      prevProps.match.params.ship !== props.match.params.ship
    ) {
      this.hasAskedForMessages = false;

      if (props.envelopes.length < 100) {
        this.askForMessages();
      }

      this.setState(
        { scrollLocked: false },
        () => {
          this.scrollToBottom();
        }
      );
    } else if (props.chatInitialized &&
               !(props.station in props.inbox) &&
               (!!props.chatSynced && !(props.station in props.chatSynced))) {

      props.history.push("/~chat");
    } else if (
      props.envelopes.length >= prevProps.envelopes.length + 10
    ) {
      this.hasAskedForMessages = false;
    }

    // FF logic
    if (
      navigator.userAgent.includes("Firefox") &&
      (props.length !== prevProps.length ||
       props.envelopes.length !== prevProps.envelopes.length ||
       getNumPending(props) !== this.lastNumPending ||
       state.numPages !== prevState.numPages)
    ) {
      if(state.scrollBottom) {
        setTimeout(() => {
          this.scrollToBottom();
        })
      } else {
        this.recalculateScrollTop();
      }

      this.lastNumPending = getNumPending(props);
    }
  }

  askForMessages() {
    const { props, state } = this;

    if (props.envelopes.length === 0) {
      setTimeout(() => {
        this.askForMessages();
      }, 500);
      return;
    }

    if (
      props.envelopes.length >= props.length ||
      this.hasAskedForMessages ||
      props.length <= 0
    ) {
      return;
    }

    let start =
      props.length - props.envelopes[props.envelopes.length - 1].number;
    if (start > 0) {
      let end = start + 300 < props.length ? start + 300 : props.length;
      this.hasAskedForMessages = true;
      props.subscription.fetchMessages(start + 1, end, props.station);
    }
  }

  scrollToBottom() {
    if (!this.state.scrollLocked && this.scrollElement) {
      this.scrollElement.scrollIntoView();
    }
  }

  // Restore chat position on FF when new messages come in
  recalculateScrollTop() {
    if(!this.scrollContainer) {
      return;
    }

    const { lastScrollHeight } = this.state;
    let target = this.scrollContainer;
    let newScrollTop = this.scrollContainer.scrollHeight - lastScrollHeight;
    if(target.scrollTop !== 0 || newScrollTop === target.scrollTop) {
      return;
    }
    target.scrollTop = target.scrollHeight - lastScrollHeight;

  }

  onScroll(e) {
    if (
      (navigator.userAgent.includes("Safari") &&
      navigator.userAgent.includes("Chrome")) ||
      navigator.userAgent.includes("Firefox")
    ) {
      // Google Chrome and Firefox
      if (e.target.scrollTop === 0) {

        // Save scroll position for FF
        if (navigator.userAgent.includes('Firefox')) {

          this.setState({
            lastScrollHeight: e.target.scrollHeight
          })
        }
        this.setState(
          {
            numPages: this.state.numPages + 1,
            scrollLocked: true
          },
          () => {
            this.askForMessages();
          }
        );
      } else if (
        e.target.scrollHeight - Math.round(e.target.scrollTop) ===
        e.target.clientHeight
      ) {
        this.setState({
          numPages: 1,
          scrollLocked: false,
          scrollBottom: true
        });
      } else if (navigator.userAgent.includes('Firefox')) {
        this.setState({ scrollBottom: false });
      }
    } else if (navigator.userAgent.includes("Safari")) {
      // Safari
      if (e.target.scrollTop === 0) {
        this.setState({
          numPages: 1,
          scrollLocked: false
        });<commit_msg>chat-js: fix scrolling to bottom<commit_after>import React, { Component } from 'react';
import classnames from 'classnames';
import _ from 'lodash';
import moment from 'moment';

import { Route, Link } from "react-router-dom";
import { store } from "/store";

import { ResubscribeElement } from '/components/lib/resubscribe-element';
import { BacklogElement } from '/components/lib/backlog-element';
import { Message } from '/components/lib/message';
import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
import { ChatTabBar } from '/components/lib/chat-tabbar';
import { ChatInput } from '/components/lib/chat-input';
import { UnreadNotice } from '/components/lib/unread-notice';
import { deSig } from '/lib/util';

function getNumPending(props) {
  const result = props.pendingMessages.has(props.station)
    ? props.pendingMessages.get(props.station).length
    : 0;
  return result;
}

export class ChatScreen extends Component {
  constructor(props) {
    super(props);

    this.state = {
      numPages: 1,
      scrollLocked: false,
      // only for FF
      lastScrollHeight: null,
    };

    this.hasAskedForMessages = false;
    this.lastNumPending = 0;

    this.scrollContainer = null;
    this.onScroll = this.onScroll.bind(this);

    this.unreadMarker = null;

    moment.updateLocale('en', {
      calendar: {
        sameDay: '[Today]',
        nextDay: '[Tomorrow]',
        nextWeek: 'dddd',
        lastDay: '[Yesterday]',
        lastWeek: '[Last] dddd',
        sameElse: 'DD/MM/YYYY'
      }
    });

  }

  componentDidMount() {
    this.askForMessages();
    this.scrollToBottom();
  }


  componentDidUpdate(prevProps, prevState) {
    const { props, state } = this;

    if (
      prevProps.match.params.station !== props.match.params.station ||
      prevProps.match.params.ship !== props.match.params.ship
    ) {
      this.hasAskedForMessages = false;

      if (props.envelopes.length < 100) {
        this.askForMessages();
      }

      this.setState(
        { scrollLocked: false },
        () => {
          this.scrollToBottom();
        }
      );
    } else if (props.chatInitialized &&
               !(props.station in props.inbox) &&
               (!!props.chatSynced && !(props.station in props.chatSynced))) {

      props.history.push("/~chat");
    } else if (
      props.envelopes.length >= prevProps.envelopes.length + 10
    ) {
      this.hasAskedForMessages = false;
    }

    if (
      (props.length !== prevProps.length ||
       props.envelopes.length !== prevProps.envelopes.length ||
       getNumPending(props) !== this.lastNumPending ||
       state.numPages !== prevState.numPages)
    ) {
      this.scrollToBottom();
      if(navigator.userAgent.includes("Firefox")) {
        this.recalculateScrollTop();
      }

      this.lastNumPending = getNumPending(props);
    }
  }

  askForMessages() {
    const { props, state } = this;

    if (props.envelopes.length === 0) {
      setTimeout(() => {
        this.askForMessages();
      }, 500);
      return;
    }

    if (
      props.envelopes.length >= props.length ||
      this.hasAskedForMessages ||
      props.length <= 0
    ) {
      return;
    }

    let start =
      props.length - props.envelopes[props.envelopes.length - 1].number;
    if (start > 0) {
      let end = start + 300 < props.length ? start + 300 : props.length;
      this.hasAskedForMessages = true;
      props.subscription.fetchMessages(start + 1, end, props.station);
    }
  }

  scrollToBottom() {
    if (!this.state.scrollLocked && this.scrollElement) {
      this.scrollElement.scrollIntoView();
    }
  }

  // Restore chat position on FF when new messages come in
  recalculateScrollTop() {
    if(!this.scrollContainer) {
      return;
    }

    const { lastScrollHeight } = this.state;
    let target = this.scrollContainer;
    let newScrollTop = this.scrollContainer.scrollHeight - lastScrollHeight;
    if(target.scrollTop !== 0 || newScrollTop === target.scrollTop) {
      return;
    }
    target.scrollTop = target.scrollHeight - lastScrollHeight;

  }

  onScroll(e) {
    if (
      (navigator.userAgent.includes("Safari") &&
      navigator.userAgent.includes("Chrome")) ||
      navigator.userAgent.includes("Firefox")
    ) {
      // Google Chrome and Firefox
      if (e.target.scrollTop === 0) {

        // Save scroll position for FF
        if (navigator.userAgent.includes('Firefox')) {

          this.setState({
            lastScrollHeight: e.target.scrollHeight
          })
        }
        this.setState(
          {
            numPages: this.state.numPages + 1,
            scrollLocked: true
          },
          () => {
            this.askForMessages();
          }
        );
      } else if (
        e.target.scrollHeight - Math.round(e.target.scrollTop) ===
        e.target.clientHeight
      ) {
        this.setState({
          numPages: 1,
          scrollLocked: false,
        });
      }
    } else if (navigator.userAgent.includes("Safari")) {
      // Safari
      if (e.target.scrollTop === 0) {
        this.setState({
          numPages: 1,
          scrollLocked: false
        });
{'fraction_non_alphanumeric': 0.13314447592067988, 'fraction_numerical': 0.005288007554296506, 'mean_word_length': 2.275904732446644, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5619071', 'n_tokens_mistral': 3278, 'n_tokens_neox': 3169, 'n_words': 603}