<commit_before>/**
 * Features module.
 * @module base/features
 */

import observable from 'riot-observable'
import { isElement } from './utils/check'
import eventHub from './eventHub'
import passiveEvents from './utils/device'

import { ATTR_FEATURES, ATTR_FEATURES_IGNORE } from './variables'

export var features = {}

/**
 * Default initialization options.
 *
 * @type {Object}
 * @property {Boolean} justChildNodes=false
 *   Set to true if you don't want to initialize the features of the container node.
 */
export var defaultInitOptions = {
  justChildNodes: false
}

/**
 * Default destroy options.
 *
 * @type {Object}
 * @property {Boolean} justChildNodes=false
 *   Set to true if you don't want to destroy the features of the container node.
 */
export var defaultDestroyOptions = {
  justChildNodes: false
}

/**
 * Reinitializes features.
 *
 * @param {Node} [container=document.body]
 *   Container element to filter where features should be reinitialized.
 * @param {String} [name=null]
 *   Comma separated string with names of the features
 *   (used by the `data-feature` attribute) which sould be reinitialized.
 */
export function reinit(container = document.body, name = null) {
  destroy(container, name)
  init(container, name)
}

/**
 * Initializes features.
 *
 * @example
 * // initialize all features
 * base.features.init()
 * @example
 * // initialize `feature1` and `feature2` instances inside #wrapper
 * base.features.init(document.getElementById('wrapper'), 'feature1,feature2')
 *
 * @param {Node} [container=document.body] Container element
 *   Container element to filter where features should be initialized.
 * @param {String} [name=null]
 *   Comma separated string with names of the features
 *   (used by the `data-feature` attribute) which sould be initialized.
 * @param {Object} [options={}]
 *   Further initialize options to overwrite the [default ones]{@link module:base/features.defaultInitOptions}.
 *
 * @returns {Array} Initialized feature instances.
 */
export function init(container = document.body, name = null, options = {}) {
  options = Object.assign({}, defaultInitOptions, options)

  var instances = []
  var names = name ? name.split(',') : null
  var featureNodes = [...container.querySelectorAll(`[${ATTR_FEATURES}]`)]

  if (!options.justChildNodes && container.getAttribute(ATTR_FEATURES)) {
    featureNodes.push(container)
  }

  eventHub.trigger('features:initialize', {
    container: container,
    names: names,
    nodes: featureNodes
  })

  featureNodes.forEach(featureNode => {
    var nodeInstances = []
    var dataFeatures = featureNode.getAttribute(ATTR_FEATURES).split(',')
    var ignoreFeatures = (
      featureNode.getAttribute(ATTR_FEATURES_IGNORE) || ''
    ).split(',')

    dataFeatures.forEach(function(featureName) {
      featureName = featureName.trim()
      var feature = features[featureName]

      if (
        !feature || // feature has not been added yet
        (ignoreFeatures && ignoreFeatures.indexOf(featureName) > -1) || // feature is ignored on this node
        (name && names.indexOf(featureName) < 0) || // name is not whitelisted
        (featureNode._baseFeatureInstances && // feature has already been initalized on this node
          featureNode._baseFeatureInstances[featureName])
      )
        return

      var instance = new feature.featureClass(
        featureName,
        featureNode,
        feature.options
      )

      instance.init()
      instances.push(instance)
      nodeInstances.push(instance)
    })

    // trigger event on all instances
    nodeInstances.forEach(function(nodeInstance) {
      nodeInstance.trigger('featuresInitialized', nodeInstances)
    })
  })

  eventHub.trigger('features:initialized', {
    container: container,
    names: names,
    nodes: featureNodes,
    instances: instances
  })

  return instances
}

/**
 * Destroy feature instances.
 *
 * @example
 * // destroy all feature instances
 * base.features.destroy()
 * @example
 * // destroy `feature1` and `feature2` instances inside #wrapper
 * base.features.destroy(document.getElementById('wrapper'), 'feature1,feature2')
 *
 * @param {Node} [container=document.body] Container element
 *   Container element to filter where features should be destroyed.
 * @param {String} [name=null]
 *   Comma separated string with names of the features
 *   (used by the `data-feature` attribute) which sould be initialized.
 * @param {Object} [options={}]
 *   Further destroy options to overwrite the [default ones]{@link module:base/features.defaultDestroyOptions}.
 */
export function destroy(container = document.body, name = null, options = {}) {
  options = Object.assign({}, defaultDestroyOptions, options)

  var names = name ? name.split(',') : null
  var featureNodes = [...container.querySelectorAll(`[${ATTR_FEATURES}]`)]

  if (!options.justChildNodes && container.getAttribute(ATTR_FEATURES)) {
    featureNodes.push(container)
  }

  eventHub.trigger('features:destroy', {
    container: container,
    names: names,
    nodes: featureNodes
  })

  featureNodes.forEach(featureNode => {
    var nodeInstances = getInstancesByNode(featureNode)
    var ignoreFeatures = (
      featureNode.getAttribute(ATTR_FEATURES_IGNORE) || ''
    ).split(',')

    for (let featureName in nodeInstances) {
      if (
        nodeInstances.hasOwnProperty(featureName) &&
        (!name || names.indexOf(featureName) > -1) && // name is whitelisted
        (!ignoreFeatures || ignoreFeatures.indexOf(featureName) < 0) && // feature is ignore on this node
        (featureNode._baseFeatureInstances && // feature instance exists
          featureNode._baseFeatureInstances[featureName])
      ) {
        nodeInstances[featureName].destroy()
        nodeInstances[featureName] = null
      }
    }
  })

  eventHub.trigger('features:destroyed', {
    container: container,
    names: names,
    nodes: featureNodes
  })
}

/**
 * Add feature
 *
 * @example
 * // add feature `deathStar`
 * base.features.add('deathStar', DeathStar, { destroyAlderaan: true })
 *
 * @param {String} name
 *   Name of the feature used by the `data-feature` attribute.
 * @param {Feature} featureClass
 *   Feature class to initiate.
 * @param {Object} options
 *   Any options to initialize the feature with.
 */
export function add(name, featureClass, options = {}) {
  if (features[name]) {
    throw new Error('Feature "' + name + '" has been already added!')
  }

  features[name] = { featureClass, options }
}

/**
 * Return all initialized feature instances from given node.
 *
 * @example
 * // get all the feature instances
 * var features = base.features.getInstancesByNode(document.getElementById('deathstar'))
 * // do something with one of the features
 * features.deathStar.destroy()
 *
 * @param {Node} node
 *   Node to return the instances from.
 * @returns {Object|null}
 *   Feature instances indexed by name (used by `data-feature` attribute).
 */
export function getInstancesByNode(node) {
  return node._baseFeatureInstances || null
}

/**
 * Return initialized feature instance from given node and name.
 *
 * @example
 * // get feature instance
 * var deathStar = base.features.getInstancesByNode(document.getElementById('deathstar'), 'deathStar')
 * // do something with the feature
 * deathStar.destroy()
 *
 * @param {Node} node
 *   Node to return the instance from.
 * @param {String} name
 *   Name used by `data-feature` attribute.
 *
 * @returns {module:base/features~Feature|null} Feature instance.
 */
export function getInstanceByNode(node, name) {
  if (!node._baseFeatureInstances) {
    return null
  }

  return node._baseFeatureInstances[name] || null
}

/**
 * Abstract Feature class.
 * @abstract
 */
export class Feature {
  /**
   * Constructor.
   *
   * @param {String} name
   *   Name of the feature used by the `data-feature` attribute.
   * @param {Node} node
   *   Node the feature belongs to.
   * @param {Object} options
   *   Feature options which can be used for anything.
   */
  constructor(name, node, options) {
    if (this.constructor === Feature) {
      throw new Error("Can't instantiate abstract class!")
    }

    observable(this)

    var defaultOptions = this.constructor.defaultOptions || {}

    this._name = name
    this._node = node
    this._options = Object.assign({}, defaultOptions, options)

    this._hubEvents = {}
    this._eventListener = {}

    if (!this._node._baseFeatureInstances) {
      this._node._baseFeatureInstances = {}
    }

    this._node._baseFeatureInstances[name] = this
  }

  /**
   * Return name the feature has been initialized with.
   * @returns {String}
   */
  get name() {
    return this._name
  }

  /**
   * Return node the feature belongs to.
   * @returns {Node}
   */
  get node() {
    return this._node
  }

  /**
   * Replaces current feature node with given one.
   * @param {Node} node - Replacement ndoe.<commit_msg>Update docs<commit_after>/**
 * Features module.
 * @module base/features
 */

import observable from 'riot-observable'
import { isElement } from './utils/check'
import eventHub from './eventHub'
import passiveEvents from './utils/device'

import { ATTR_FEATURES, ATTR_FEATURES_IGNORE } from './variables'

export var features = {}

/**
 * Default initialization options.
 *
 * @type {Object}
 * @property {Boolean} justChildNodes=false
 *   Set to true if you don't want to initialize the features of the container node.
 */
export var defaultInitOptions = {
  justChildNodes: false
}

/**
 * Default destroy options.
 *
 * @type {Object}
 * @property {Boolean} justChildNodes=false
 *   Set to true if you don't want to destroy the features of the container node.
 */
export var defaultDestroyOptions = {
  justChildNodes: false
}

/**
 * Reinitializes features.
 *
 * @param {Node} [container=document.body]
 *   Container element to filter where features should be reinitialized.
 * @param {String} [name=null]
 *   Comma separated string with names of the features
 *   (used by the `data-feature` attribute) which sould be reinitialized.
 */
export function reinit(container = document.body, name = null) {
  destroy(container, name)
  init(container, name)
}

/**
 * Initializes features.
 *
 * @example
 * // initialize all features
 * base.features.init()
 * @example
 * // initialize `feature1` and `feature2` instances inside #wrapper
 * base.features.init(document.getElementById('wrapper'), 'feature1,feature2')
 *
 * @param {Node} [container=document.body] Container element
 *   Container element to filter where features should be initialized.
 * @param {String} [name=null]
 *   Comma separated string with names of the features
 *   (used by the `data-feature` attribute) which sould be initialized.
 * @param {Object} [options={}]
 *   Further initialize options to overwrite the [default ones]{@link module:base/features.defaultInitOptions}.
 *
 * @returns {Array} Initialized feature instances.
 */
export function init(container = document.body, name = null, options = {}) {
  options = Object.assign({}, defaultInitOptions, options)

  var instances = []
  var names = name ? name.split(',') : null
  var featureNodes = [...container.querySelectorAll(`[${ATTR_FEATURES}]`)]

  if (!options.justChildNodes && container.getAttribute(ATTR_FEATURES)) {
    featureNodes.push(container)
  }

  eventHub.trigger('features:initialize', {
    container: container,
    names: names,
    nodes: featureNodes
  })

  featureNodes.forEach(featureNode => {
    var nodeInstances = []
    var dataFeatures = featureNode.getAttribute(ATTR_FEATURES).split(',')
    var ignoreFeatures = (
      featureNode.getAttribute(ATTR_FEATURES_IGNORE) || ''
    ).split(',')

    dataFeatures.forEach(function(featureName) {
      featureName = featureName.trim()
      var feature = features[featureName]

      if (
        !feature || // feature has not been added yet
        (ignoreFeatures && ignoreFeatures.indexOf(featureName) > -1) || // feature is ignored on this node
        (name && names.indexOf(featureName) < 0) || // name is not whitelisted
        (featureNode._baseFeatureInstances && // feature has already been initalized on this node
          featureNode._baseFeatureInstances[featureName])
      )
        return

      var instance = new feature.featureClass(
        featureName,
        featureNode,
        feature.options
      )

      instance.init()
      instances.push(instance)
      nodeInstances.push(instance)
    })

    // trigger event on all instances
    nodeInstances.forEach(function(nodeInstance) {
      nodeInstance.trigger('featuresInitialized', nodeInstances)
    })
  })

  eventHub.trigger('features:initialized', {
    container: container,
    names: names,
    nodes: featureNodes,
    instances: instances
  })

  return instances
}

/**
 * Destroy feature instances.
 *
 * @example
 * // destroy all feature instances
 * base.features.destroy()
 * @example
 * // destroy `feature1` and `feature2` instances inside #wrapper
 * base.features.destroy(document.getElementById('wrapper'), 'feature1,feature2')
 *
 * @param {Node} [container=document.body] Container element
 *   Container element to filter where features should be destroyed.
 * @param {String} [name=null]
 *   Comma separated string with names of the features
 *   (used by the `data-feature` attribute) which sould be initialized.
 * @param {Object} [options={}]
 *   Further destroy options to overwrite the [default ones]{@link module:base/features.defaultDestroyOptions}.
 */
export function destroy(container = document.body, name = null, options = {}) {
  options = Object.assign({}, defaultDestroyOptions, options)

  var names = name ? name.split(',') : null
  var featureNodes = [...container.querySelectorAll(`[${ATTR_FEATURES}]`)]

  if (!options.justChildNodes && container.getAttribute(ATTR_FEATURES)) {
    featureNodes.push(container)
  }

  eventHub.trigger('features:destroy', {
    container: container,
    names: names,
    nodes: featureNodes
  })

  featureNodes.forEach(featureNode => {
    var nodeInstances = getInstancesByNode(featureNode)
    var ignoreFeatures = (
      featureNode.getAttribute(ATTR_FEATURES_IGNORE) || ''
    ).split(',')

    for (let featureName in nodeInstances) {
      if (
        nodeInstances.hasOwnProperty(featureName) &&
        (!name || names.indexOf(featureName) > -1) && // name is whitelisted
        (!ignoreFeatures || ignoreFeatures.indexOf(featureName) < 0) && // feature is ignore on this node
        (featureNode._baseFeatureInstances && // feature instance exists
          featureNode._baseFeatureInstances[featureName])
      ) {
        nodeInstances[featureName].destroy()
        nodeInstances[featureName] = null
      }
    }
  })

  eventHub.trigger('features:destroyed', {
    container: container,
    names: names,
    nodes: featureNodes
  })
}

/**
 * Add feature
 *
 * @example
 * // add feature `deathStar`
 * base.features.add('deathStar', DeathStar, { destroyAlderaan: true })
 *
 * @param {String} name
 *   Name of the feature used by the `data-feature` attribute.
 * @param {Feature} featureClass
 *   Feature class to initiate.
 * @param {Object} options
 *   Any options to initialize the feature with.
 */
export function add(name, featureClass, options = {}) {
  if (features[name]) {
    throw new Error('Feature "' + name + '" has been already added!')
  }

  features[name] = { featureClass, options }
}

/**
 * Return all initialized feature instances from given node.
 *
 * @example
 * // get all the feature instances
 * var features = base.features.getInstancesByNode(document.getElementById('deathstar'))
 * // do something with one of the features
 * features.deathStar.destroy()
 *
 * @param {Node} node
 *   Node to return the instances from.
 * @returns {Object|null}
 *   Feature instances indexed by name (used by `data-feature` attribute).
 */
export function getInstancesByNode(node) {
  return node._baseFeatureInstances || null
}

/**
 * Return initialized feature instance from given node and name.
 *
 * @example
 * // get feature instance
 * var deathStar = base.features.getInstancesByNode(document.getElementById('deathstar'), 'deathStar')
 * // do something with the feature
 * deathStar.destroy()
 *
 * @param {Node} node
 *   Node to return the instance from.
 * @param {String} name
 *   Name used by `data-feature` attribute.
 *
 * @returns {module:base/features~Feature|null} Feature instance.
 */
export function getInstanceByNode(node, name) {
  if (!node._baseFeatureInstances) {
    return null
  }

  return node._baseFeatureInstances[name] || null
}

/**
 * Abstract Feature class.
 * @abstract
 */
export class Feature {
  /**
   * Constructor.
   *
   * @param {String} name
   *   Name of the feature used by the `data-feature` attribute.
   * @param {Node} node
   *   Node the feature belongs to.
   * @param {Object} options
   *   Feature options which can be used for anything.
   */
  constructor(name, node, options) {
    if (this.constructor === Feature) {
      throw new Error("Can't instantiate abstract class!")
    }

    observable(this)

    var defaultOptions = this.constructor.defaultOptions || {}

    this._name = name
    this._node = node
    this._options = Object.assign({}, defaultOptions, options)

    this._hubEvents = {}
    this._eventListener = {}

    if (!this._node._baseFeatureInstances) {
      this._node._baseFeatureInstances = {}
    }

    this._node._baseFeatureInstances[name] = this
  }

  /**
   * Return name the feature has been initialized with.
   * @returns {String}
   */
  get name() {
    return this._name
  }

  /**
   * Return node the feature belongs to.
   * @returns {Node}
   */
  get node() {
    return this._node
  }

  /**
   * Replaces current feature node with given one.
   * @param {Node} node - Replacement node.
{'fraction_non_alphanumeric': 0.11933281092578081, 'fraction_numerical': 0.0013433337064815852, 'mean_word_length': 3.4891959798994976, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, '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': '5167963', 'n_tokens_mistral': 5019, 'n_tokens_neox': 4974, 'n_words': 1440}