<commit_before>import { Component, without } from 'substance'
import entityRenderers from './entityRenderers'

export default class EntitySelector extends Component {

  getInitialState() {
    return {
      searchString: '',
      results: []
    }
  }

  render($$) {
    let el = $$('div').addClass('sc-entity-selector')

    let inputEl = $$('div').addClass('se-input-field').append(
      $$('input')
        .attr({
          type: 'text',
          value: this.state.searchString,
          placeholder: this.props.placeholder
        })
        .on('input', this._onSearchStringChanged)
        .on('keydown', this._onKeydown)
        .ref('searchString')
    )

    if (this.state.searchString !== '') {
      let createBtns = $$('div').addClass('se-create')
      this.props.targetTypes.forEach(targetType => {
        createBtns.append(
          $$('button').append('Create '+targetType)
            .on('click', this._triggerCreate.bind(this, targetType))
        )
      })
      el.append(
        inputEl.append(createBtns),
        this._renderOptions($$)
      )
    } else {
      el.append(inputEl)
    }

    return el
  }

  _renderOptions($$) {
    let el = $$('div').addClass('se-options')
    let db = this.context.db

    this.state.results.forEach(entity => {
      // let entity = db.get(entityId)

      let option = $$('div').addClass('se-option').html(
        entityRenderers[entity.type](entity.id, db)
      ).on('click', this._selectOption.bind(this, entity.id))

      if(this.state.selected === entity.id) {
        option.addClass('se-selected')
      }

      el.append(option)
    })

    // // Render options for creation
    // this.props.targetTypes.forEach(targetType => {
    //   el.append(
    //     $$('div').addClass('se-option').append(
    //       $$('button').append('Create '+targetType)
    //     )
    //     .on('click', this._triggerCreate.bind(this, targetType))
    //   )
    // })
    return el
  }

  _selectOption(entityId) {
    this.props.onSelected(entityId)
  }

  _selectNext() {
    const selection = this.state.selected
    const results = this.state.results
    if(results.length > 0) {
      let selectedEntity
      if(selection) {
        const selectionIndex = results.findIndex(item => {
          return item.id === selection
        })

        if(selectionIndex < results.length - 1) {
          selectedEntity = results[selectionIndex + 1]
        } else {
          selectedEntity = results[0]
        }
      } else {
        selectedEntity = results[0]
      }

      this.extendState({selected: selectedEntity.id})
    }
  }

  _selectPrevious() {
    const selection = this.state.selected
    const results = this.state.results
    if(results.length > 0) {
      let selectedEntity
      if(selection) {
        const selectionIndex = results.findIndex(item => {
          return item.id === selection
        })

        if(selectionIndex > 0) {
          selectedEntity = results[selectionIndex - 1]
        } else {
          selectedEntity = results[results.length - 1]
        }
      } else {
        selectedEntity = results[results.length - 1]
      }

      this.extendState({selected: selectedEntity.id})
    }
  }

  _triggerCreate(targetType) {
    this.props.onCreate(targetType)
  }

  // TODO: For the current prorotype we use a naive regexp based filtering,
  // but we should allow full text search here
  _findEntities(searchString) {
    let db = this.context.db
    let availableEntities = []
    this.props.targetTypes.forEach(targetType => {
      availableEntities = availableEntities.concat(
        db.find({ type: targetType })
      )
    })
    availableEntities = without(availableEntities, ...this.props.excludes)
      .map(entityId => {
        return db.get(entityId)
      })
      .filter(entity => {
        let htmlString = entityRenderers[entity.type](entity.id, db)
        return htmlString.match(new RegExp(searchString, 'i'))
      })
    return availableEntities
  }

  _onSearchStringChanged() {
    let searchString = this.refs.searchString.val()
    let results = []
    if (searchString) {
      results = this._findEntities(searchString)
    }

    this.setState({
      searchString: searchString,
      results
    })
  }

  _onKeydown(e) {
    if (e.keyCode === 38) {
      e.preventDefault()
      this._selectPrevious()
    } else if (e.keyCode === 40) {
      e.preventDefault()
      this._selectNext()
    } else if (e.keyCode === 13 && this.state.selected) {
      this._selectOption(this.state.selected)
    }
  }
}
<commit_msg>Allow entity creation via keyboard nav.<commit_after>import { Component, without } from 'substance'
import entityRenderers from './entityRenderers'

export default class EntitySelector extends Component {

  getInitialState() {
    return {
      searchString: '',
      results: [],
      selectedIndex: -1
    }
  }

  render($$) {
    let el = $$('div').addClass('sc-entity-selector')

    let inputEl = $$('div').addClass('se-input-field').append(
      $$('input')
        .attr({
          type: 'text',
          value: this.state.searchString,
          placeholder: this.props.placeholder
        })
        .on('input', this._onSearchStringChanged)
        .on('keydown', this._onKeydown)
        .ref('searchString')
    )

    if (this.state.searchString !== '') {
      let createBtns = $$('div').addClass('se-create')
      el.append(
        inputEl.append(createBtns),
        this._renderOptions($$)
      )
    } else {
      el.append(inputEl)
    }

    return el
  }

  _renderOptions($$) {
    let el = $$('div').addClass('se-options').ref('options')
    let db = this.context.db
    this.state.results.forEach((item, index) => {
      let option
      if (item._create) {
        option = $$('div').addClass('se-option').append(
          `Create ${item._create}`
        )
      } else {
        option = $$('div').addClass('se-option').html(
          entityRenderers[item.type](item.id, db)
        )
      }
      option.on('click', this._confirmSelected.bind(this, index))
      if (this.state.selectedIndex === index) {
        option.addClass('se-selected')
      }
      el.append(option)
      index += 1
    })
    return el
  }

  _confirmSelected(index) {
    let item = this.state.results[index]
    if (item._create) {
      this.props.onCreate(item._create)
    } else {
      this.props.onSelected(item.id)
    }
  }

  _selectNext() {
    let selectedIndex = this.state.selectedIndex
    let results = this.state.results
    if (selectedIndex < results.length - 1) {
      selectedIndex += 1
    }
    this.extendState({
      selectedIndex: selectedIndex
    })
  }

  _selectPrevious() {
    let selectedIndex = this.state.selectedIndex
    if (selectedIndex >= 0) {
      selectedIndex -= 1
    }
    this.extendState({
      selectedIndex: selectedIndex
    })
  }

  // TODO: For the current prorotype we use a naive regexp based filtering,
  // but we should allow full text search here
  _findEntities(searchString) {
    let db = this.context.db
    let availableEntities = []
    this.props.targetTypes.forEach(targetType => {
      availableEntities = availableEntities.concat(
        db.find({ type: targetType })
      )
    })
    availableEntities = without(availableEntities, ...this.props.excludes)
      .map(entityId => {
        return db.get(entityId)
      })
      .filter(entity => {
        let htmlString = entityRenderers[entity.type](entity.id, db)
        return htmlString.match(new RegExp(searchString, 'i'))
      })
    return availableEntities
  }

  _onSearchStringChanged() {
    let searchString = this.refs.searchString.val()
    let results = []
    if (searchString) {
      results = this._findEntities(searchString)
    }

    // Add creation entries to result
    this.props.targetTypes.forEach(targetType => {
      results.push({
        _create: targetType
      })
    })

    this.setState({
      selectedIndex: -1,
      searchString: searchString,
      results
    })
  }

  _onKeydown(e) {
    if (e.keyCode === 38) {
      e.preventDefault()
      this._selectPrevious()
    } else if (e.keyCode === 40) {
      e.preventDefault()
      this._selectNext()
    } else if (e.keyCode === 13 && this.state.selectedIndex >= 0) {
      this._confirmSelected(this.state.selectedIndex)
    }
  }
}

{'fraction_non_alphanumeric': 0.13805479124297165, 'fraction_numerical': 0.0035889460461777725, 'mean_word_length': 2.2913385826771653, 'pattern_counts': {'":': 0, '<': 5, '<?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': '3285735', 'n_tokens_mistral': 2530, 'n_tokens_neox': 2494, 'n_words': 467}