Source: managers/TextManager.js

import document from 'global/document';

import HT from '../HT';

import { getPlainText, isChildren, checkException } from '../utils';

import 'style-loader!css-loader!sass-loader!../style/hover.scss';
import hoverCssCode from 'css-to-string-loader!css-loader!../style/hover.scss';
import { EventEmitter } from 'events';

class TextManager extends EventEmitter {
  ht = null;
  hasMoved = false;
  addedListeners = false;

  constructor(ht) {
    super();
    this.ht = ht;
  }

  _activated = false;
  get activated() { return this._activated; }
  set activated(value) {
    this._activated = value;
    if (value) {

      // Faz a checagem dos iframes apenas uma vez
      if (!this.addedListeners) {
        // Adiciona os event Listeners no frame principal
        this.addEventListeners(document);
        this.addedListeners = true;
      }
      this.ht.emit('activated', 'textManager');
    }
  }

  /**
   * Captura o texto de um elemento, caso seja imagem captura o alt
   * @param elem - Elemento a ter o texto capturado
   */
  getText = (elem) => {
    const { tagName } = elem;
    const { exceptions } = this.ht.config;
    const lowerTagName = (tagName || '').toLowerCase();

    // Verifica se o ElementInspector está ativado
    if (!this.activated) {
      this.ht.logger.debug('TextManager desativado');
      return false;
    }

    // Verifica se o elemento está na lista de exceções
    if (!checkException(exceptions, elem)) {
      this.ht.logger.debug('Elemento é uma exceção');
      return false;
    }

    // Verifica se o elemento está na lista de tags proibidas
    if (lowerTagName && HT.skipTags.indexOf(lowerTagName) >= 0) {
      this.ht.logger.debug('Tag do elemento é', lowerTagName);
      return false;
    }

    // Caso elemento seja uma imagem iremos fazer a tradução do ALT
    if (lowerTagName === 'img') {
      const imgText = elem.getAttribute('data-ht-alt') || elem.getAttribute('alt');
      return this.checkTextSize(imgText) ? imgText : false;
    }

    // Elemento de texto padrão (span, p, div, ...)
    if (!this.checkTextSize(elem.innerText)) return false;

    // Verifica se o elemento é filho de um .ht-skip
    if (isChildren(elem, '.ht-skip')) {
      this.ht.logger.debug('Elemento tem ou é filho de um .ht-skip', lowerTagName);
      return false;
    }

    const plainText = getPlainText(elem);

    return this.checkTextSize(plainText) ? plainText : false;
  };

  /** 
   * Verifica se a quantidade de caracteres é menor que o estipulado e maior que 0
   * @param {String} text - Texto a ter o tamanho verificado.
   * @returns {Boolean} 
  */
  checkTextSize = (text) => {
    if (text.length > 0 && text.length < this.ht.config.maxTextSize) return true;
    this.ht.logger.debug('O Elemento possui muitos ou nenhum caractere dentro do texto', text.length);
    return false;
  }

  /** 
   * Quando o mouse passa sobre um elemento - 
   * Document ou iframe.contentDocument do elemento 
   * precisa estar vinculado por this.addEventListeners().
   */
  
  onMouseOver = ({ target }) => {
    if (this.activated && this.getText(target)) this.emit('validElementOver', target);
  };

  /** 
   * Quando o mouse sai de um elemento - 
   * Document ou iframe.contentDocument do elemento 
   * precisa estar vinculado por this.addEventListeners().
   */
  onMouseOut = ({ target }) => {
    if (this.activated) this.emit('validElementOut', target);
  };

  /** 
   * Quando um elemento é clicado - 
   * Document ou iframe.contentDocument do elemento 
   * precisa estar vinculado por this.addEventListeners().
   */
  onClick = (e) => {
    if (!this.activated) return false;

    const { target } = e;
    const text = this.getText(target, true);

    if (!text) return false;

    this.emit('validElementClicked', e, text);

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

  /** Quando o touch está se movendo  */
  onTouchMove = () => { 
    if (this.activated) this.hasMoved = true;
  };

  /** Quando o touch sai da tela  */
  onTouchEnd = (e) => {
    if (!this.activated) return false;

    if (!this.hasMoved) this.onClick(e);
    this.hasMoved = false;
  }


  /**
   * Adiciona os EventListeners em um frame especifico
   * @param frame - Frame que receberá os EventListeners.
   */
  addEventListeners = (frame) => {
    if (HT.deviceInfo.isMobile) {
      frame.addEventListener('touchmove', this.onTouchMove, true);
      frame.addEventListener('touchend', this.onTouchEnd, true);
    } else {
      frame.addEventListener('mouseover', this.onMouseOver);
      frame.addEventListener('mouseout', this.onMouseOut);
      frame.addEventListener('click', this.onClick, true);
    }
    this.ht.logger.info('EventListeners adicionados no frame', frame);
  }

  /** 
   * Adiciona o css de hover em um iframe
   * @param iframe - Frame alvo
   */
  addExtraStyle = (iframe) => {
    const head = iframe.contentDocument.head || iframe.contentDocument.querySelector('head');
    const styleElem = iframe.contentDocument.createElement('style');
    styleElem.type = 'text/css';
    styleElem.appendChild(iframe.contentDocument.createTextNode(hoverCssCode));
    head.appendChild(styleElem);
  }

  /**
   * Adiciona os EventListeners em um iframe especifico
   * @param iframe - iframe que receberá os listeners
   */
  addIframeListeners = (iframe) => {
    if (!iframe.getAttribute('data-ht-bindedevents')) {
      this.addEventListeners(iframe.contentDocument);
      this.addExtraStyle(iframe);
      iframe.setAttribute('data-ht-bindedevents', true);
    } else this.ht.logger.info('Ignorando iframe que ja possui os Event Listeners', iframe);
  }

  /**
   * Adiciona EventListener nos frames encontrados com o atributo data-ht-bindedevents = false
   */
  addIframesListenersAll = () => {
    Array.prototype.forEach.bind(document.body.querySelectorAll('iframe'))((iframe) => {
      try {
        this.addIframeListeners(iframe);
      } catch (e) {
        this.ht.logger.warn('Falha ao adicionar EventListener no iframe ' + e);
      }
    });
  };

  /**
   * Remove os EventListeners de um frame especifico - Desktop e Mobile.
   * @param frame - Frame que terá os Event Listeners removidos.
   */
  removeEventListener = (frame) => {
    if (HT.deviceInfo.isMobile) {
      frame.removeEventListener('touchmove', this.onTouchMove, true);
      frame.removeEventListener('touchend', this.onTouchEnd, true);
    } else {
      frame.removeEventListener('mouseover', this.onMouseOver);
      frame.removeEventListener('mouseout', this.onMouseOut);
      frame.removeEventListener('click', this.onClick, true);
    }
  }

  destroy = () => {
    this.removeEventListener(document);
    Array.prototype.forEach.bind(document.body.querySelectorAll('iframe'))((iframe) => {
      try {
        this.removeEventListener(iframe.contentDocument);
        iframe.removeAttribute('data-ht-bindedevents');
      } catch (e) {
        this.ht.logger.warn('Falha ao remover EventListener no iframe ' + e);
      }
    });
    this.activated = false;
    this.ht.logger.info('TextManager destruido');
  };
}

export default TextManager;