<commit_before>import Hint from './hint';
import HintKeyProducer from './hint-key-producer';

const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz';

export default class Follow {
  constructor(doc) {
    this.doc = doc;
    this.hintElements = {};
    this.keys = [];
    this.onActivatedCallbacks = [];

    // TODO activate input elements and push button elements
    let links = Follow.getTargetElements(doc);

    this.addHints(links);

    this.boundKeydown = this.handleKeydown.bind(this);
    doc.addEventListener('keydown', this.boundKeydown);
  }

  addHints(elements) {
    let producer = new HintKeyProducer(DEFAULT_HINT_CHARSET);
    Array.prototype.forEach.call(elements, (ele) => {
      let keys = producer.produce();
      let hint = new Hint(ele, keys);

      this.hintElements[keys] = hint;
    });
  }

  handleKeydown(e) {
    let { keyCode } = e;
    if (keyCode === KeyboardEvent.DOM_VK_ESCAPE) {
      this.remove();
      return;
    } else if (keyCode === KeyboardEvent.DOM_VK_ENTER ||
               keyCode === KeyboardEvent.DOM_VK_RETURN) {
      let chars = Follow.codeChars(this.keys);
      this.activate(this.hintElements[chars].target);
      return;
    } else if (Follow.availableKey(keyCode)) {
      this.keys.push(keyCode);
    } else if (keyCode === KeyboardEvent.DOM_VK_BACK_SPACE ||
               keyCode === KeyboardEvent.DOM_VK_DELETE) {
      this.keys.pop();
    }

    e.stopPropagation();
    e.preventDefault();

    this.refreshKeys();
  }

  refreshKeys() {
    let chars = Follow.codeChars(this.keys);
    let shown = Object.keys(this.hintElements).filter((key) => {
      return key.startsWith(chars);
    });
    let hidden = Object.keys(this.hintElements).filter((key) => {
      return !key.startsWith(chars);
    });
    if (shown.length === 0) {
      this.remove();
      return;
    } else if (shown.length === 1) {
      this.remove();
      this.activate(this.hintElements[chars].target);
    }

    shown.forEach((key) => {
      this.hintElements[key].show();
    });
    hidden.forEach((key) => {
      this.hintElements[key].hide();
    });
  }

  remove() {
    this.doc.removeEventListener('keydown', this.boundKeydown);
    Object.keys(this.hintElements).forEach((key) => {
      this.hintElements[key].remove();
    });
  }

  activate(element) {
    this.onActivatedCallbacks.forEach(f => f(element));
  }

  onActivated(f) {
    this.onActivatedCallbacks.push(f);
  }

  static availableKey(keyCode) {
    return (
      KeyboardEvent.DOM_VK_0 <= keyCode && keyCode <= KeyboardEvent.DOM_VK_9 ||
      KeyboardEvent.DOM_VK_A <= keyCode && keyCode <= KeyboardEvent.DOM_VK_Z
    );
  }

  static isNumericKey(code) {
    return KeyboardEvent.DOM_VK_0 <= code && code <= KeyboardEvent.DOM_VK_9;
  }

  static isAlphabeticKey(code) {
    return KeyboardEvent.DOM_VK_A <= code && code <= KeyboardEvent.DOM_VK_Z;
  }

  static codeChars(codes) {
    const CHARCODE_ZERO = '0'.charCodeAt(0);
    const CHARCODE_A = 'a'.charCodeAt(0);

    let chars = '';

    for (let code of codes) {
      if (Follow.isNumericKey(code)) {
        chars += String.fromCharCode(
          code - KeyboardEvent.DOM_VK_0 + CHARCODE_ZERO);
      } else if (Follow.isAlphabeticKey(code)) {
        chars += String.fromCharCode(
          code - KeyboardEvent.DOM_VK_A + CHARCODE_A);
      }
    }
    return chars;
  }

  static getTargetElements(doc) {
    let all = doc.querySelectorAll('a,button,input,textarea');
    let filtered = Array.prototype.filter.call(all, (element) => {
      let style = window.getComputedStyle(element);
      return style.display !== 'none' &&
        style.visibility !== 'hidden' &&
        element.type !== 'hidden';
    });
    return filtered;
  }
}<commit_msg>show hints for elements in viewport<commit_after>import Hint from './hint';
import HintKeyProducer from './hint-key-producer';

const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz';

export default class Follow {
  constructor(doc) {
    this.doc = doc;
    this.hintElements = {};
    this.keys = [];
    this.onActivatedCallbacks = [];

    // TODO activate input elements and push button elements
    let links = Follow.getTargetElements(doc);

    this.addHints(links);

    this.boundKeydown = this.handleKeydown.bind(this);
    doc.addEventListener('keydown', this.boundKeydown);
  }

  addHints(elements) {
    let producer = new HintKeyProducer(DEFAULT_HINT_CHARSET);
    Array.prototype.forEach.call(elements, (ele) => {
      let keys = producer.produce();
      let hint = new Hint(ele, keys);

      this.hintElements[keys] = hint;
    });
  }

  handleKeydown(e) {
    let { keyCode } = e;
    if (keyCode === KeyboardEvent.DOM_VK_ESCAPE) {
      this.remove();
      return;
    } else if (keyCode === KeyboardEvent.DOM_VK_ENTER ||
               keyCode === KeyboardEvent.DOM_VK_RETURN) {
      let chars = Follow.codeChars(this.keys);
      this.activate(this.hintElements[chars].target);
      return;
    } else if (Follow.availableKey(keyCode)) {
      this.keys.push(keyCode);
    } else if (keyCode === KeyboardEvent.DOM_VK_BACK_SPACE ||
               keyCode === KeyboardEvent.DOM_VK_DELETE) {
      this.keys.pop();
    }

    e.stopPropagation();
    e.preventDefault();

    this.refreshKeys();
  }

  refreshKeys() {
    let chars = Follow.codeChars(this.keys);
    let shown = Object.keys(this.hintElements).filter((key) => {
      return key.startsWith(chars);
    });
    let hidden = Object.keys(this.hintElements).filter((key) => {
      return !key.startsWith(chars);
    });
    if (shown.length === 0) {
      this.remove();
      return;
    } else if (shown.length === 1) {
      this.remove();
      this.activate(this.hintElements[chars].target);
    }

    shown.forEach((key) => {
      this.hintElements[key].show();
    });
    hidden.forEach((key) => {
      this.hintElements[key].hide();
    });
  }

  remove() {
    this.doc.removeEventListener('keydown', this.boundKeydown);
    Object.keys(this.hintElements).forEach((key) => {
      this.hintElements[key].remove();
    });
  }

  activate(element) {
    this.onActivatedCallbacks.forEach(f => f(element));
  }

  onActivated(f) {
    this.onActivatedCallbacks.push(f);
  }

  static availableKey(keyCode) {
    return (
      KeyboardEvent.DOM_VK_0 <= keyCode && keyCode <= KeyboardEvent.DOM_VK_9 ||
      KeyboardEvent.DOM_VK_A <= keyCode && keyCode <= KeyboardEvent.DOM_VK_Z
    );
  }

  static isNumericKey(code) {
    return KeyboardEvent.DOM_VK_0 <= code && code <= KeyboardEvent.DOM_VK_9;
  }

  static isAlphabeticKey(code) {
    return KeyboardEvent.DOM_VK_A <= code && code <= KeyboardEvent.DOM_VK_Z;
  }

  static codeChars(codes) {
    const CHARCODE_ZERO = '0'.charCodeAt(0);
    const CHARCODE_A = 'a'.charCodeAt(0);

    let chars = '';

    for (let code of codes) {
      if (Follow.isNumericKey(code)) {
        chars += String.fromCharCode(
          code - KeyboardEvent.DOM_VK_0 + CHARCODE_ZERO);
      } else if (Follow.isAlphabeticKey(code)) {
        chars += String.fromCharCode(
          code - KeyboardEvent.DOM_VK_A + CHARCODE_A);
      }
    }
    return chars;
  }

  static inWindow(window, element) {
    let {
      top, left, bottom, right
    } = element.getBoundingClientRect();
    return (
      top >= 0 && left >= 0 &&
      bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      right <= (window.innerWidth || document.documentElement.clientWidth)
    );
  }

  static getTargetElements(doc) {
    let all = doc.querySelectorAll('a,button,input,textarea');
    let filtered = Array.prototype.filter.call(all, (element) => {
      let style = window.getComputedStyle(element);
      return style.display !== 'none' &&
        style.visibility !== 'hidden' &&
        element.type !== 'hidden' &&
        Follow.inWindow(window, element);
    });
    return filtered;
  }
}
{'fraction_non_alphanumeric': 0.14262191260291324, 'fraction_numerical': 0.002786573780873971, 'mean_word_length': 2.920556107249255, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, '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': '3788954', 'n_tokens_mistral': 2648, 'n_tokens_neox': 2572, 'n_words': 437}