<commit_before>import { get, extend, isEmpty, delay, result, omit } from 'lodash';

// Note that the GetPositionMixin methods are called directly rather than mixing it in
// since we're a mixin ourselves our consumers also include GetPosition and it causes
// duplicate method name errors to mix it in
import { GetPositionMixin } from 'shared';

const DEFAULT_DURATION   = 750; // milliseconds
// This is calculated to be enough for the targeted element to fit under the top navbar
// The navbar's height is controlled by the less variable @tutor-navbar-height from global/navbar.less
const DEFAULT_TOP_OFFSET = 80; // pixels

// Attempt to scroll to element no more than this number of times.
// In testing, no more than one attempt has been needed but it's best to have a failsafe to
// ensure scrolling doesn't enter an infinite loop
const MAXIMUM_SCROLL_ATTEMPTS = 3;

// http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
function EASE_IN_OUT(t) {
  if (t < .5) { return 4 * t * t * t; } else { return ((t - 1) * ((2 * t) - 2) * ((2 * t) - 2)) + 1; }
}

function POSITION(start, end, elapsed, duration) {
  if (elapsed > duration) { return end; }
  return start + ((end - start) * EASE_IN_OUT(elapsed / duration));
}

export default class ScrollTo {

  constructor({ windowImpl = window, onAfterScroll, root, scrollingTargetClass } = {}) {
    this.onAfterScroll = onAfterScroll;
    this.windowImpl = windowImpl;
    this.root = root || this.windowImpl.document.body;
    this.scrollingTargetClass = scrollingTargetClass;
  }

  scrollToSelector(selector, options) {
    options = extend({
      updateHistory: true, unlessInView: false,
    }, options);

    const el = this.getElement(selector);
    if (el && (!options.unlessInView || !this.isElementInView(el))) {
      return this.scrollToElement(el, options);
    }
    return false;
  }

  isSelectorInView(selector) {
    const el = this.getElement(selector);
    if (!el) { return false; }

    return this.isElementInView(el);
  }

  isElementInView(el) {
    const { top, bottom, height } = el.getBoundingClientRect();
    const visibleHeight = Math.min(window.innerHeight, bottom) - Math.max(0, top);

    return visibleHeight > (height / 2);
  }

  getElement(selector) {
    if (isEmpty(selector)) { return null; }
    return this.root.querySelector(selector);
  }

  _onBeforeScroll(el) {
    if (this.scrollingTargetClass) { el.classList.add(this.scrollingTargetClass); }
    // 'target-scroll');
    return (typeof this.onBeforeScroll === 'function' ? this.onBeforeScroll(el) : undefined);
  }

  _onAfterScroll(el, options) {
    const cl = get(el, 'classList');
    if (cl && cl.contains('target-scroll')) {
      delay(cl.remove.bind(el.classList, 'target-scroll'), 150);
    }
    if (options.updateHistory) {
      this.windowImpl.history.pushState(null, null, `#${el.id}`);
    }
    if (this.whenScrollCompete) {
      this.whenScrollCompete();
      this.whenScrollCompete = null;
    }
    return (typeof this.onAfterScroll === 'function' ? this.onAfterScroll(el) : undefined);

  }

  _onScrollStep(el, options) {
    // The element's postion may have changed if scrolling was initiated while
    // the page was still being manipulated.
    // If that's the case, we begin another scroll to it's current position
    if ((options.attemptNumber < MAXIMUM_SCROLL_ATTEMPTS) && (this.windowImpl.pageYOffset !== this._desiredTopPosition(el, options))) {
      return this.scrollToElement(el, extend(options, { attemptNumber: options.attemptNumber + 1 }));
    } else {
      return this._onAfterScroll(el, options);
    }
  }

  _desiredTopPosition(el, options ) {
    if (options == null) { options = {}; }

    return GetPositionMixin.getTopPosition(el) - (
      options.scrollTopOffset || result(this, 'getScrollTopOffset', DEFAULT_TOP_OFFSET)
    );
  }

  scrollToTop({ deferred = false } = {}) {
    const root = this.windowImpl.document.body.querySelector('#ox-react-root-container');
    if (root) {
      return this.scrollToElement(root, { updateHistory: false, deferred });
    }
    return Promise.resolve();
  }

  // deferedScrollToTop() {
  //   delay(() => this.scrollToTop(), 10);
  // }
  //
  scrollToElement(el, options = {}) {
    if (!el.parentElement) { return Promise.resolve(); } // the element has been deleted
    if (options.deferred) {
      return new Promise(resolve => {
        delay(() => {
          this.scrollToElement(el, omit(options, 'deferred'), 10).then(resolve);
        });
      });
    }
    const win       = this.windowImpl;
    const endPos    = this._desiredTopPosition(el, options);<commit_msg>return promise, delete commented code<commit_after>import { get, extend, isEmpty, delay, result, omit } from 'lodash';

// Note that the GetPositionMixin methods are called directly rather than mixing it in
// since we're a mixin ourselves our consumers also include GetPosition and it causes
// duplicate method name errors to mix it in
import { GetPositionMixin } from 'shared';

const DEFAULT_DURATION   = 750; // milliseconds
// This is calculated to be enough for the targeted element to fit under the top navbar
// The navbar's height is controlled by the less variable @tutor-navbar-height from global/navbar.less
const DEFAULT_TOP_OFFSET = 80; // pixels

// Attempt to scroll to element no more than this number of times.
// In testing, no more than one attempt has been needed but it's best to have a failsafe to
// ensure scrolling doesn't enter an infinite loop
const MAXIMUM_SCROLL_ATTEMPTS = 3;

// http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
function EASE_IN_OUT(t) {
  if (t < .5) { return 4 * t * t * t; } else { return ((t - 1) * ((2 * t) - 2) * ((2 * t) - 2)) + 1; }
}

function POSITION(start, end, elapsed, duration) {
  if (elapsed > duration) { return end; }
  return start + ((end - start) * EASE_IN_OUT(elapsed / duration));
}

export default class ScrollTo {

  constructor({ windowImpl = window, onAfterScroll, root, scrollingTargetClass } = {}) {
    this.onAfterScroll = onAfterScroll;
    this.windowImpl = windowImpl;
    this.root = root || this.windowImpl.document.body;
    this.scrollingTargetClass = scrollingTargetClass;
  }

  scrollToSelector(selector, options) {
    options = extend({
      updateHistory: true, unlessInView: false,
    }, options);

    const el = this.getElement(selector);
    if (el && (!options.unlessInView || !this.isElementInView(el))) {
      return this.scrollToElement(el, options);
    }
    return Promise.resolve(false);
  }

  isSelectorInView(selector) {
    const el = this.getElement(selector);
    if (!el) { return false; }

    return this.isElementInView(el);
  }

  isElementInView(el) {
    const { top, bottom, height } = el.getBoundingClientRect();
    const visibleHeight = Math.min(window.innerHeight, bottom) - Math.max(0, top);

    return visibleHeight > (height / 2);
  }

  getElement(selector) {
    if (isEmpty(selector)) { return null; }
    return this.root.querySelector(selector);
  }

  _onBeforeScroll(el) {
    if (this.scrollingTargetClass) { el.classList.add(this.scrollingTargetClass); }
    // 'target-scroll');
    return (typeof this.onBeforeScroll === 'function' ? this.onBeforeScroll(el) : undefined);
  }

  _onAfterScroll(el, options) {
    const cl = get(el, 'classList');
    if (cl && cl.contains('target-scroll')) {
      delay(cl.remove.bind(el.classList, 'target-scroll'), 150);
    }
    if (options.updateHistory) {
      this.windowImpl.history.pushState(null, null, `#${el.id}`);
    }
    if (this.whenScrollCompete) {
      this.whenScrollCompete();
      this.whenScrollCompete = null;
    }
    return (typeof this.onAfterScroll === 'function' ? this.onAfterScroll(el) : undefined);

  }

  _onScrollStep(el, options) {
    // The element's postion may have changed if scrolling was initiated while
    // the page was still being manipulated.
    // If that's the case, we begin another scroll to it's current position
    if ((options.attemptNumber < MAXIMUM_SCROLL_ATTEMPTS) && (this.windowImpl.pageYOffset !== this._desiredTopPosition(el, options))) {
      return this.scrollToElement(el, extend(options, { attemptNumber: options.attemptNumber + 1 }));
    } else {
      return this._onAfterScroll(el, options);
    }
  }

  _desiredTopPosition(el, options ) {
    if (options == null) { options = {}; }

    return GetPositionMixin.getTopPosition(el) - (
      options.scrollTopOffset || result(this, 'getScrollTopOffset', DEFAULT_TOP_OFFSET)
    );
  }

  scrollToTop({ deferred = false } = {}) {
    const root = this.windowImpl.document.body.querySelector('#ox-react-root-container');
    if (root) {
      return this.scrollToElement(root, { updateHistory: false, deferred });
    }
    return Promise.resolve();
  }

  scrollToElement(el, options = {}) {
    if (!el.parentElement) { return Promise.resolve(); } // the element has been deleted
    if (options.deferred) {
      return new Promise(resolve => {
        delay(() => {
          this.scrollToElement(el, omit(options, 'deferred'), 10).then(resolve);
        });
      });
    }
    const win       = this.windowImpl;
    const endPos    = this._desiredTopPosition(el, options);
{'fraction_non_alphanumeric': 0.12869583915707988, 'fraction_numerical': 0.006235888614127513, 'mean_word_length': 3.4337464251668255, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 12, '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': '2622506', 'n_tokens_mistral': 2911, 'n_tokens_neox': 2785, 'n_words': 757}