yuku-t/textcomplete

View on GitHub
src/strategy.js

Summary

Maintainability
A
0 mins
Test Coverage
// @flow

declare class MatchData extends Array<string> {
  index: number;
}

export type { MatchData }

const DEFAULT_INDEX = 2

function DEFAULT_TEMPLATE(value) {
  return value
}

/**
 * Properties for a strategy.
 *
 * @typedef
 */
export type StrategyProperties = {
  match: RegExp | (string => MatchData | null),
  search: Function,
  replace: any => string[] | string | null,
  cache?: boolean,
  context?: Function,
  template?: any => string,
  index?: number,
  id?: string,
}

/**
 * Encapsulate a single strategy.
 */
export default class Strategy {
  props: StrategyProperties
  cache: ?Object

  constructor(props: StrategyProperties) {
    this.props = props
    this.cache = props.cache ? {} : null
  }

  /**
   * @return {this}
   */
  destroy() {
    this.cache = null
    return this
  }

  search(term: string, callback: Function, match: MatchData): void {
    if (this.cache) {
      this.searchWithCache(term, callback, match)
    } else {
      this.props.search(term, callback, match)
    }
  }

  /**
   * @param {object} data - An element of array callbacked by search function.
   */
  replace(data: any) {
    return this.props.replace(data)
  }

  /** @private */
  searchWithCache(term: string, callback: Function, match: MatchData): void {
    if (this.cache && this.cache[term]) {
      callback(this.cache[term])
    } else {
      this.props.search(
        term,
        results => {
          if (this.cache) {
            this.cache[term] = results
          }
          callback(results)
        },
        match,
      )
    }
  }

  /** @private */
  matchText(text: string): MatchData | null {
    if (typeof this.match === "function") {
      return this.match(text)
    } else {
      return (text.match(this.match): any)
    }
  }

  /** @private */
  get match(): $PropertyType<StrategyProperties, "match"> {
    return this.props.match
  }

  /** @private */
  get index(): number {
    return typeof this.props.index === "number"
      ? this.props.index
      : DEFAULT_INDEX
  }

  get template(): (...any) => string {
    return this.props.template || DEFAULT_TEMPLATE
  }
}