<commit_before>
import Encoder from '../Encoder'
import InvalidInputError from '../Error/InvalidInput'
import MathUtil from '../MathUtil'

const meta = {
  name: 'bootstring',
  title: 'Bootstring',
  category: 'Encoding',
  type: 'encoder'
}

const minBase = 2

const integerOverflowMessage =
  'Integer overflow: Input needs wider integers to process'

/**
 * Encoder brick for Bootstring encoding and decoding following RFC 3492.
 */
export default class BootstringEncoder extends Encoder {
  /**
   * Returns brick meta.
   * @return {object}
   */
  static getMeta () {
    return meta
  }

  /**
   * Constructor
   */
  constructor () {
    super()
    this.addSettings([
      {
        name: 'basicRangeStart',
        label: 'Basic Start',
        type: 'number',
        value: 0,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: 0,
          max: 127 - minBase
        }
      },
      {
        name: 'basicRangeEnd',
        label: 'Basic End',
        type: 'number',
        value: 127,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: minBase,
          // Max Unicode code point value
          max: 0x10FFFF
        }
      },
      {
        name: 'digitMapping',
        type: 'alphabet',
        value: 'abcdefghijklmnopqrstuvwxyz0123456789',
        randomizable: false,
        validateValue: this.validateDigitMappingValue.bind(this)
      },
      {
        name: 'delimiter',
        type: 'text',
        value: '-',
        width: 6,
        randomizable: false,
        validateValue: this.validateDelimiterValue.bind(this),
        options: {
          minLength: 1,
          maxLength: 1
        }
      },
      {
        name: 'caseSensitivity',
        type: 'boolean',
        value: false,
        width: 6,
        randomizable: false
      },
      {
        name: 'initialBias',
        type: 'number',
        value: 72,
        width: 6,
        randomizable: false,
        validateValue: this.validateInitialBiasValue.bind(this),
        options: {
          integer: true
        }
      },
      {
        name: 'initialN',
        type: 'number',
        value: 128,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          min: 0
        }
      },
      {
        name: 'tmin',
        type: 'number',
        value: 1,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: 0,
          max: 26
        }
      },
      {
        name: 'tmax',
        type: 'number',
        value: 26,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: 0,
          max: 35
        }
      },
      {
        name: 'skew',
        type: 'number',
        value: 38,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          min: 1
        }
      },
      {
        name: 'damp',
        type: 'number',
        value: 700,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          min: 2
        }
      }
    ])
  }

  /**
   * Performs encode on given content following section 6.3 of RFC 3492.
   * @protected
   * @param {Chain} content
   * @return {number[]|string|Uint8Array|Chain|Promise} Encoded content
   */
  performEncode (content) {
    const {
      initialBias,
      initialN,
      tmin,
      tmax,
      caseSensitivity
    } = this.getSettingValues()

    const delimiter = this.getSettingValue('delimiter').getCodePointAt(0)
    const base = this.getSettingValue('digitMapping').getLength()

    // Prepare content
    if (!caseSensitivity) {
      content = content.toLowerCase()
    }

    // Initialize the state
    let n = initialN
    let bias = initialBias
    let delta = 0

    // Copy basic code points in the input to the output in order
    const input = content.getCodePoints()
    const inputLength = input.length
    const output = []
    const nonBasicInput = []
    let codePoint

    for (let i = 0; i < input.length; i++) {
      codePoint = input[i]
      if (this._isBasic(codePoint)) {
        output.push(codePoint)
      } else if (codePoint >= n) {
        nonBasicInput.push(codePoint)
      } else {
        throw new InvalidInputError(
          `Unexpected code point at index ${i}, consider changing initial n ` +
          `to include this code point`)
      }
    }

    // Sort non-basic input code points in ascending order
    nonBasicInput.sort()

    let h = output.length
    let b = output.length
    if (b > 0) {
      output.push(delimiter)
    }

    let m, q, k, t, j
    while (h < inputLength) {
      m = nonBasicInput.find(codePoint => codePoint >= n)

      // Overflow detection
      if (m - n > MathUtil.div(Number.MAX_SAFE_INTEGER - delta, h + 1)) {
        throw new InvalidInputError(integerOverflowMessage)
      }

      delta += (m - n) * (h + 1)
      n = m

      for (j = 0; j < inputLength; j++) {
        codePoint = input[j]
        if (codePoint < n || this._isBasic(codePoint)) {
          // Overflow detection
          if (++delta > Number.MAX_SAFE_INTEGER) {
            throw new InvalidInputError(integerOverflowMessage)
          }
        }
        if (codePoint === n) {
          q = delta
          for (k = base; true; k += base) {
            t = this._calcT(k, bias, tmin, tmax)
            if (q < t) {
              break
            }
            output.push(this._basicCodePointFromDigit(
              t + MathUtil.mod(q - t, base - t)))
            q = MathUtil.div(q - t, base - t)
          }
          output.push(this._basicCodePointFromDigit(q))
          bias = this._adaptBias(delta, h + 1, h === b)
          delta = 0
          h++
        }
      }

      delta++
      n++
    }

    return output
  }

  /**
   * Performs decode on given content following section 6.2 of RFC 3492.
   * @protected
   * @param {Chain} content
   * @return {number[]|string|Uint8Array|Chain|Promise} Decoded content
   */
  performDecode (content) {
    const {
      initialBias,
      initialN,
      tmin,
      tmax,
      caseSensitivity
    } = this.getSettingValues()

    const delimiter = this.getSettingValue('delimiter').getCodePointAt(0)
    const base = this.getSettingValue('digitMapping').getLength()

    // Prepare case insensitive content
    if (!caseSensitivity) {
      content = content.toLowerCase()
    }

    // Initialize the state
    let n = initialN
    let bias = initialBias

    // Consume all code points before the last delimiter (if there is one)
    // and copy them to output, fail on any non-basic code point
    const input = content.getCodePoints()
    const inputLength = input.length
    const basicLength = Math.max(input.lastIndexOf(delimiter), 0)
    const output = []

    for (let j = 0; j < basicLength; j++) {
      if (!this._isBasic(input[j])) {
        throw new InvalidInputError(
          `Found unexpected non-basic code point at ${j}`)
      }
      output.push(input[j])
    }

    let i = 0
    let j = basicLength > 0 ? basicLength + 1 : 0
    let oldi, w, k, digit, t

    while (j < inputLength) {
      oldi = i
      w = 1

      for (k = base; true; k += base) {
        // Fail if there is no code point to consume next
        if (j >= inputLength) {
          throw new InvalidInputError('The input ends unexpectedly')
        }

        digit = this._digitFromBasicCodePoint(input[j++])

        if (digit === -1) {
          throw new InvalidInputError(
            `Found unexpected non-basic code point at ${j - 1}`)
        }

        // Overflow detection
        if (digit > MathUtil.div(Number.MAX_SAFE_INTEGER - i, w)) {
          throw new InvalidInputError(integerOverflowMessage)
        }

        i += digit * w

        t = this._calcT(k, bias, tmin, tmax)
        if (digit < t) {
          break
        }

        // Overflow detection
        if (w > MathUtil.div(Number.MAX_SAFE_INTEGER, base - t)) {
          throw new InvalidInputError(integerOverflowMessage)
        }

        w *= base - t
      }

      bias = this._adaptBias(i - oldi, output.length + 1, oldi === 0)

      // Overflow detection
      if (MathUtil.div(i, output.length + 1) > Number.MAX_SAFE_INTEGER - n) {
        throw new InvalidInputError(integerOverflowMessage)
      }

      n += MathUtil.div(i, output.length + 1)
      i = MathUtil.mod(i, output.length + 1)

      // If n is a basic code point then fail
      if (this._isBasic(n)) {
        throw new InvalidInputError('Unexpectedly unwrapped basic code point')
      }

      output.splice(i, 0, n)
      i++
    }

    return output
  }

  /**
   * Bias adaptation function following section 6.1 of RFC 3492.
   * After each delta is encoded or decoded, bias is set for the next delta.
   * @param {number} delta
   * @param {number} numPoints
   * @param {boolean} firstTime
   * @return {number}
   */
  _adaptBias (delta, numPoints, firstTime) {
    const { digitMapping, tmin, tmax, skew } = this.getSettingValues()
    const base = digitMapping.getLength()

    if (firstTime) {
      const damp = this.getSettingValue('damp')
      delta = MathUtil.div(delta, damp)
    } else {
      delta = MathUtil.div(delta, 2)
    }

    delta += MathUtil.div(delta, numPoints)

    let k = 0
    while (delta > MathUtil.div((base - tmin) * tmax, 2)) {
      delta = MathUtil.div(delta, base - tmin)
      k += base
    }

    return k + MathUtil.div((base - tmin + 1) * delta, delta + skew)
  }

  /**
   * Chooses the next T value based on given parameters.
   * @param {number} k
   * @param {number} bias
   * @param {number} tmin
   * @param {number} tmax
   * @return {number}
   */
  _calcT (k, bias, tmin, tmax) {
    if (k <= bias + tmin) {
      return tmin
    } else if (k >= bias + tmax) {
      return tmax
    }
    return k - bias
  }

  /**
   * Checks wether the given code point is considered to be basic.
   * @param {number} codePoint Unicode code point
   * @return {boolean} True, if the given code point is basic
   */
  _isBasic (codePoint) {
    const { basicRangeStart, basicRangeEnd } = this.getSettingValues()
    return codePoint >= basicRangeStart && codePoint <= basicRangeEnd
  }

  /**
   * Translates a Unicode code point into a basic code point digit.
   * @param {number} codePoint Unicode code point
   * @return {number} Basic code point digit or -1, if there is no digit
   * assigned to the given Unicode code point
   */
  _digitFromBasicCodePoint (codePoint) {
    return this.getSettingValue('digitMapping').indexOfCodePoint(codePoint)
  }

  /**
   * Translates a basic code point digit into a Unicode code point.
   * @param {number} digit Basic code point digit
   * @return {number} Unicode code point
   */
  _basicCodePointFromDigit (digit) {
    return this.getSettingValue('digitMapping').getCodePointAt(digit)
  }

  /**
   * Triggered when a setting field has changed.
   * @protected
   * @param {Field} setting Sender setting field
   * @param {mixed} value New field value
   */
  settingValueDidChange (setting, value) {
    super.settingValueDidChange(setting, value)
    switch (setting.getName()) {
      case 'basicRangeStart':
        this.getSetting('basicRangeEnd').setMin(setting.getValue() + minBase)
        this.getSetting('digitMapping').revalidateValue()
        this.getSetting('delimiter').revalidateValue()
        break
      case 'basicRangeEnd':
        this.getSetting('basicRangeStart').setMax(setting.getValue() - minBase)
        this.getSetting('digitMapping').revalidateValue()
        this.getSetting('delimiter').revalidateValue()
        break
      case 'digitMapping':
        this.getSetting('tmax').setMax(setting.getValue().getLength())
        break
      case 'tmax':
        this.getSetting('tmin').setMax(setting.getValue())
        break
    }
  }

  /**
   * Validates the digit mapping value.
   * @param {Chain} value Value to be validated
   * @param {Field} setting Sender field
   * @return {boolean|object}
   */
  validateDigitMappingValue (value, setting) {
    const basicRangeStart = this.getSetting('basicRangeStart')
    const basicRangeEnd = this.getSetting('basicRangeEnd')

    if (!basicRangeStart.isValid() || !basicRangeEnd.isValid()) {
      // Can't validate digit mapping without valid basic range
      return false
    }

    const rangeStart = basicRangeStart.getValue()
    const rangeEnd = basicRangeEnd.getValue()

    const invalidIndex = value.getCodePoints().findIndex(codePoint =>
      codePoint < rangeStart || codePoint > rangeEnd)

    if (invalidIndex !== -1) {
      return {
        key: 'bootstringDigitMappingInvalid',
        message:
          `Character at index ${invalidIndex} needs to be part of the given ` +
          `basic code point range`
      }
    }

    return true
  }

  /**
   * Validates the delimiter value.
   * @param {Chain} value Delimiter value
   * @param {Field} setting Sender field
   * @return {boolean|object}
   */
  validateDelimiterValue (value, setting) {
    const basicRangeStart = this.getSetting('basicRangeStart')
    const basicRangeEnd = this.getSetting('basicRangeEnd')
    const digitMapping = this.getSetting('digitMapping')

    if (!basicRangeStart.isValid() ||
        !basicRangeEnd.isValid() ||
        !digitMapping.isValid()) {
      // Can't validate digit mapping without valid basic range
      return false
    }

    const delimiter = value.getCodePointAt(0)

    // Delimiter needs to be in the basic code point range
    if (delimiter < basicRangeStart.getValue() ||
        delimiter > basicRangeEnd.getValue() ||
        digitMapping.getValue().indexOfCodePoint(delimiter) !== -1) {
      return {
        key: 'bootstringDelimiterInvalid',
        message:
          `The value must be part of the basic code point range while ` +
          `not having a digit mapped to it`
      }
    }

    return true
  }

  /**
   * Validates initial bias setting value according to section 4 of RFC 3492
   * such that `initial_bias mod base <= base - tmin`.
   * @param {number} value Initial bias to be validated
   * @param {Field} setting Sender field
   * @return {boolean|object}
   */
  validateInitialBiasValue (value, setting) {
    const digitMapping = this.getSetting('digitMapping')
    const tmin = this.getSetting('tmin')

    if (!digitMapping.isValid() || !tmin.isValid()) {
      // Can't validate initial bias without valid base and tmin values
      return false
    }

    const base = digitMapping.getValue().getLength()

    if (MathUtil.mod(value, base) > base - tmin) {
      return {
        key: 'bootstringInitialBiasInvalid',
        message:
          'The value must be chosen such that ' +
          'initial_bias mod base <= base - tmin'
      }
    }

    return true
  }
}
<commit_msg>Fixes a Bootstrap issue causing an infinite loop during encode<commit_after>
import Encoder from '../Encoder'
import InvalidInputError from '../Error/InvalidInput'
import MathUtil from '../MathUtil'

const meta = {
  name: 'bootstring',
  title: 'Bootstring',
  category: 'Encoding',
  type: 'encoder'
}

const minBase = 2

const integerOverflowMessage =
  'Integer overflow: Input needs wider integers to process'

/**
 * Encoder brick for Bootstring encoding and decoding following RFC 3492.
 */
export default class BootstringEncoder extends Encoder {
  /**
   * Returns brick meta.
   * @return {object}
   */
  static getMeta () {
    return meta
  }

  /**
   * Constructor
   */
  constructor () {
    super()
    this.addSettings([
      {
        name: 'basicRangeStart',
        label: 'Basic Start',
        type: 'number',
        value: 0,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: 0,
          max: 127 - minBase
        }
      },
      {
        name: 'basicRangeEnd',
        label: 'Basic End',
        type: 'number',
        value: 127,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: minBase,
          // Max Unicode code point value
          max: 0x10FFFF
        }
      },
      {
        name: 'digitMapping',
        type: 'alphabet',
        value: 'abcdefghijklmnopqrstuvwxyz0123456789',
        randomizable: false,
        validateValue: this.validateDigitMappingValue.bind(this)
      },
      {
        name: 'delimiter',
        type: 'text',
        value: '-',
        width: 6,
        randomizable: false,
        validateValue: this.validateDelimiterValue.bind(this),
        options: {
          minLength: 1,
          maxLength: 1
        }
      },
      {
        name: 'caseSensitivity',
        type: 'boolean',
        value: false,
        width: 6,
        randomizable: false
      },
      {
        name: 'initialBias',
        type: 'number',
        value: 72,
        width: 6,
        randomizable: false,
        validateValue: this.validateInitialBiasValue.bind(this),
        options: {
          integer: true
        }
      },
      {
        name: 'initialN',
        type: 'number',
        value: 128,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          min: 0
        }
      },
      {
        name: 'tmin',
        type: 'number',
        value: 1,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: 0,
          max: 26
        }
      },
      {
        name: 'tmax',
        type: 'number',
        value: 26,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          rotate: false,
          min: 0,
          max: 35
        }
      },
      {
        name: 'skew',
        type: 'number',
        value: 38,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          min: 1
        }
      },
      {
        name: 'damp',
        type: 'number',
        value: 700,
        width: 6,
        randomizable: false,
        options: {
          integer: true,
          min: 2
        }
      }
    ])
  }

  /**
   * Performs encode on given content following section 6.3 of RFC 3492.
   * @protected
   * @param {Chain} content
   * @return {number[]|string|Uint8Array|Chain|Promise} Encoded content
   */
  performEncode (content) {
    const {
      initialBias,
      initialN,
      tmin,
      tmax,
      caseSensitivity
    } = this.getSettingValues()

    const delimiter = this.getSettingValue('delimiter').getCodePointAt(0)
    const base = this.getSettingValue('digitMapping').getLength()

    // Prepare content
    if (!caseSensitivity) {
      content = content.toLowerCase()
    }

    // Initialize the state
    const input = content.getCodePoints()
    const inputLength = input.length
    let n = initialN
    let bias = initialBias
    let delta = 0

    // Copy basic code points in the input to the output in order
    const output = []
    for (let i = 0; i < input.length; i++) {
      if (this._isBasic(input[i])) {
        output.push(input[i])
      } else if (input[i] < n) {
        throw new InvalidInputError(
          `Unexpected code point at index ${i}, consider changing initial n ` +
          `to include this code point`)
      }
    }

    let h = output.length
    let b = output.length
    if (b > 0) {
      output.push(delimiter)
    }

    let m, q, k, t, c
    while (h < inputLength) {
      // Find the next larger non-basic code point >= n
      m = Number.MAX_SAFE_INTEGER
      for (c of input) {
        if (c >= n && c < m && !this._isBasic(c)) {
          m = c
        }
      }

      // Increase delta enough to advance the decoder's <n,i> state to <m,0>,
      // but guard against overflow
      if (m - n > MathUtil.div(Number.MAX_SAFE_INTEGER - delta, h + 1)) {
        throw new InvalidInputError(integerOverflowMessage)
      }
      delta += (m - n) * (h + 1)
      n = m

      for (c of input) {
        if (c < n || this._isBasic(c)) {
          // Overflow detection
          if (++delta > Number.MAX_SAFE_INTEGER) {
            throw new InvalidInputError(integerOverflowMessage)
          }
        }
        if (c === n) {
          q = delta
          for (k = base; true; k += base) {
            t = this._calcT(k, bias, tmin, tmax)
            if (q < t) {
              break
            }
            output.push(this._basicCodePointFromDigit(
              t + MathUtil.mod(q - t, base - t)))
            q = MathUtil.div(q - t, base - t)
          }
          output.push(this._basicCodePointFromDigit(q))
          bias = this._adaptBias(delta, h + 1, h === b)
          delta = 0
          h++
        }
      }

      delta++
      n++
    }

    return output
  }

  /**
   * Performs decode on given content following section 6.2 of RFC 3492.
   * @protected
   * @param {Chain} content
   * @return {number[]|string|Uint8Array|Chain|Promise} Decoded content
   */
  performDecode (content) {
    const {
      initialBias,
      initialN,
      tmin,
      tmax,
      caseSensitivity
    } = this.getSettingValues()

    const delimiter = this.getSettingValue('delimiter').getCodePointAt(0)
    const base = this.getSettingValue('digitMapping').getLength()

    // Prepare case insensitive content
    if (!caseSensitivity) {
      content = content.toLowerCase()
    }

    // Initialize the state
    const input = content.getCodePoints()
    const inputLength = input.length
    let n = initialN
    let bias = initialBias

    // Consume all code points before the last delimiter (if there is one)
    // and copy them to output, fail on any non-basic code point
    const basicLength = Math.max(input.lastIndexOf(delimiter), 0)
    const output = []

    for (let j = 0; j < basicLength; j++) {
      if (!this._isBasic(input[j])) {
        throw new InvalidInputError(
          `Found unexpected non-basic code point at ${j}`)
      }
      output.push(input[j])
    }

    let i = 0
    let j = basicLength > 0 ? basicLength + 1 : 0
    let oldi, w, k, digit, t

    while (j < inputLength) {
      oldi = i
      w = 1

      for (k = base; true; k += base) {
        // Fail if there is no code point to consume next
        if (j >= inputLength) {
          throw new InvalidInputError('The input ends unexpectedly')
        }

        digit = this._digitFromBasicCodePoint(input[j++])

        if (digit === -1) {
          throw new InvalidInputError(
            `Found unexpected non-basic code point at ${j - 1}`)
        }

        // Overflow detection
        if (digit > MathUtil.div(Number.MAX_SAFE_INTEGER - i, w)) {
          throw new InvalidInputError(integerOverflowMessage)
        }

        i += digit * w

        t = this._calcT(k, bias, tmin, tmax)
        if (digit < t) {
          break
        }

        // Overflow detection
        if (w > MathUtil.div(Number.MAX_SAFE_INTEGER, base - t)) {
          throw new InvalidInputError(integerOverflowMessage)
        }

        w *= base - t
      }

      bias = this._adaptBias(i - oldi, output.length + 1, oldi === 0)

      // Overflow detection
      if (MathUtil.div(i, output.length + 1) > Number.MAX_SAFE_INTEGER - n) {
        throw new InvalidInputError(integerOverflowMessage)
      }

      n += MathUtil.div(i, output.length + 1)
      i = MathUtil.mod(i, output.length + 1)

      // If n is a basic code point then fail
      if (this._isBasic(n)) {
        throw new InvalidInputError('Unexpectedly unwrapped basic code point')
      }

      output.splice(i, 0, n)
      i++
    }

    return output
  }

  /**
   * Bias adaptation function following section 6.1 of RFC 3492.
   * After each delta is encoded or decoded, bias is set for the next delta.
   * @param {number} delta
   * @param {number} numPoints
   * @param {boolean} firstTime
   * @return {number}
   */
  _adaptBias (delta, numPoints, firstTime) {
    const { digitMapping, tmin, tmax, skew } = this.getSettingValues()
    const base = digitMapping.getLength()

    if (firstTime) {
      const damp = this.getSettingValue('damp')
      delta = MathUtil.div(delta, damp)
    } else {
      delta = MathUtil.div(delta, 2)
    }

    delta += MathUtil.div(delta, numPoints)

    let k = 0
    while (delta > MathUtil.div((base - tmin) * tmax, 2)) {
      delta = MathUtil.div(delta, base - tmin)
      k += base
    }

    return k + MathUtil.div((base - tmin + 1) * delta, delta + skew)
  }

  /**
   * Chooses the next T value based on given parameters.
   * @param {number} k
   * @param {number} bias
   * @param {number} tmin
   * @param {number} tmax
   * @return {number}
   */
  _calcT (k, bias, tmin, tmax) {
    if (k <= bias + tmin) {
      return tmin
    } else if (k >= bias + tmax) {
      return tmax
    }
    return k - bias
  }

  /**
   * Checks wether the given code point is considered to be basic.
   * @param {number} codePoint Unicode code point
   * @return {boolean} True, if the given code point is basic
   */
  _isBasic (codePoint) {
    const { basicRangeStart, basicRangeEnd } = this.getSettingValues()
    return codePoint >= basicRangeStart && codePoint <= basicRangeEnd
  }

  /**
   * Translates a Unicode code point into a basic code point digit.
   * @param {number} codePoint Unicode code point
   * @return {number} Basic code point digit or -1, if there is no digit
   * assigned to the given Unicode code point
   */
  _digitFromBasicCodePoint (codePoint) {
    return this.getSettingValue('digitMapping').indexOfCodePoint(codePoint)
  }

  /**
   * Translates a basic code point digit into a Unicode code point.
   * @param {number} digit Basic code point digit
   * @return {number} Unicode code point
   */
  _basicCodePointFromDigit (digit) {
    return this.getSettingValue('digitMapping').getCodePointAt(digit)
  }

  /**
   * Triggered when a setting field has changed.
   * @protected
   * @param {Field} setting Sender setting field
   * @param {mixed} value New field value
   */
  settingValueDidChange (setting, value) {
    super.settingValueDidChange(setting, value)
    switch (setting.getName()) {
      case 'basicRangeStart':
        this.getSetting('basicRangeEnd').setMin(setting.getValue() + minBase)
        this.getSetting('digitMapping').revalidateValue()
        this.getSetting('delimiter').revalidateValue()
        break
      case 'basicRangeEnd':
        this.getSetting('basicRangeStart').setMax(setting.getValue() - minBase)
        this.getSetting('digitMapping').revalidateValue()
        this.getSetting('delimiter').revalidateValue()
        break
      case 'digitMapping':
        // The base depends on the digit mapping
        const base = setting.getValue().getLength()
        this.getSetting('tmax').setMax(base - 1)
        this.getSetting('delimiter').revalidateValue()
        this.getSetting('initialBias').revalidateValue()
        break
      case 'tmin':
        this.getSetting('initialBias').revalidateValue()
        break
      case 'tmax':
        this.getSetting('tmin').setMax(setting.getValue())
        break
    }
  }

  /**
   * Validates the digit mapping value.
   * @param {Chain} value Value to be validated
   * @param {Field} setting Sender field
   * @return {boolean|object}
   */
  validateDigitMappingValue (value, setting) {
    const basicRangeStart = this.getSetting('basicRangeStart')
    const basicRangeEnd = this.getSetting('basicRangeEnd')

    if (!basicRangeStart.isValid() || !basicRangeEnd.isValid()) {
      // Can't validate digit mapping without valid basic range
      return false
    }

    const rangeStart = basicRangeStart.getValue()
    const rangeEnd = basicRangeEnd.getValue()

    const invalidIndex = value.getCodePoints().findIndex(codePoint =>
      codePoint < rangeStart || codePoint > rangeEnd)

    if (invalidIndex !== -1) {
      return {
        key: 'bootstringDigitMappingInvalid',
        message:
          `Character at index ${invalidIndex} needs to be part of the given ` +
          `basic code point range`
      }
    }

    return true
  }

  /**
   * Validates the delimiter value.
   * @param {Chain} value Delimiter value
   * @param {Field} setting Sender field
   * @return {boolean|object}
   */
  validateDelimiterValue (value, setting) {
    const basicRangeStart = this.getSetting('basicRangeStart')
    const basicRangeEnd = this.getSetting('basicRangeEnd')
    const digitMapping = this.getSetting('digitMapping')

    if (!basicRangeStart.isValid() ||
        !basicRangeEnd.isValid() ||
        !digitMapping.isValid()) {
      // Can't validate digit mapping without valid basic range
      return false
    }

    const delimiter = value.getCodePointAt(0)

    // Delimiter needs to be in the basic code point range
    if (delimiter < basicRangeStart.getValue() ||
        delimiter > basicRangeEnd.getValue() ||
        digitMapping.getValue().indexOfCodePoint(delimiter) !== -1) {
      return {
        key: 'bootstringDelimiterInvalid',
        message:
          `The value must be part of the basic code point range while ` +
          `not having a digit mapped to it`
      }
    }

    return true
  }

  /**
   * Validates initial bias setting value according to section 4 of RFC 3492
   * such that `initial_bias mod base <= base - tmin`.
   * @param {number} value Initial bias to be validated
   * @param {Field} setting Sender field
   * @return {boolean|object}
   */
  validateInitialBiasValue (value, setting) {
    const digitMapping = this.getSetting('digitMapping')
    const tmin = this.getSetting('tmin')

    if (!digitMapping.isValid() || !tmin.isValid()) {
      // Can't validate initial bias without valid base and tmin values
      return false
    }

    const base = digitMapping.getValue().getLength()

    if (MathUtil.mod(value, base) > base - tmin) {
      return {
        key: 'bootstringInitialBiasInvalid',
        message:
          'The value must be chosen such that ' +
          'initial_bias mod base <= base - tmin'
      }
    }

    return true
  }
}
<|endoftext|>


<commit_before>export default function({ variants }) {
  return function({ addUtilities }) {
    addUtilities(
      {
        '.border-solid': {
          'border-style': 'solid',
        },
        '.border-dashed': {
          'border-style': 'dashed',
        },
        '.border-dotted': {
          'border-style': 'dotted',
        },
        '.border-none': {
          'border-style': 'none',
        },
      },
      variants
    )
  }
}
<commit_msg>Source borderStyle options from config<commit_after>export default function() {
  return function({ addUtilities, config }) {
    addUtilities(
      {
        '.border-solid': {
          'border-style': 'solid',
        },
        '.border-dashed': {
          'border-style': 'dashed',
        },
        '.border-dotted': {
          'border-style': 'dotted',
        },
        '.border-none': {
          'border-style': 'none',
        },
      },
      config('borderStyle.variants')
    )
  }
}
<|endoftext|>


<commit_before>import React, {
    Image,
    Text,
    View,
    Component,
    StyleSheet,
    Animated,
    PanResponder,
    TouchableHighlight,
    Dimensions
} from 'react-native';

const styles = StyleSheet.create({
    container: {
        flexDirection: 'row',
        backgroundColor: '#222'
    },
    image: {
        width: Dimensions.get('window').width
    },
    buttons: {
        height: 15,
        marginTop: -15,
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        flexDirection: 'row'
    },
    button: {
        margin: 3,
        width: 8,
        height: 8,
        borderRadius: 8 / 2,
        backgroundColor: '#ccc',
        opacity: 0.9
    },
    buttonSelected: {
        opacity: 1,
        backgroundColor: '#fff',
    }
});

export default class ImageSlider extends Component {
    constructor(props) {
        super(props);

        this.state = {
            position: 0,
            height: Dimensions.get('window').width * (4 / 9),
            left: new Animated.Value(0)
        };
    }

    _move(index) {
        let width = Dimensions.get('window').width;
        Animated.spring(this.state.left, {toValue: index * -width, friction: 10, tension: 50}).start();
        this.setState({position: index});
    }

    componentWillMount() {
        let release = (e, gestureState) => {
            let width = Dimensions.get('window').width;
            let relativeDistance = gestureState.dx / width;
            let vx = gestureState.vx;
            let change = 0;

            if (relativeDistance < -0.5 || (relativeDistance < 0 && vx <= 0.5)) {
                change = 1;
            } else if (relativeDistance > 0.5 || (relativeDistance > 0 && vx >= 0.5)) {
                change = -1;
            }
            if (this.state.position === 0 && change === -1) {
                change = 0;
            } else if (this.state.position + change >= this.props.images.length) {
                change = (this.props.images.length) - (this.state.position + change);
            }
            this._move(this.state.position + change);
        };

        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => {
                return true;
            },
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
            onPanResponderRelease: release,
            onPanResponderTerminate: release,
            onPanResponderMove: (e, gestureState) => {
                let dx = gestureState.dx;
                let width = Dimensions.get('window').width;
                this.state.left.setValue(-(this.state.position * width) + Math.round(dx));
            }
        });
    }

    render() {
        let width = Dimensions.get('window').width;
        let height = this.props.height || this.state.height;
        return (<View>
            <Animated.View
                style={[styles.container, {height: height, width: width * this.props.images.length, transform: [{translateX: this.state.left}]}]}
                {...this._panResponder.panHandlers}>
                    {this.props.images.map((image, index) => {
                        return (<Image key={index} source={{uri: image}} style={[styles.image, {height: this.state.height}]}/>)
                    })}
            </Animated.View>
            <View style={styles.buttons}>
                {this.props.images.map((image, index) => {
                    return (<TouchableHighlight
                        key={index}
                        underlayColor="#ccc"
                        onPress={() => this._move(index)}
                        style={[styles.button, this.state.position === index && styles.buttonSelected]}>
                            <View></View>
                    </TouchableHighlight>);
                })}
            </View>
        </View>);
    }
}
<commit_msg>fix image height issue when using props.height<commit_after>import React, {
    Image,
    Text,
    View,
    Component,
    StyleSheet,
    Animated,
    PanResponder,
    TouchableHighlight,
    Dimensions
} from 'react-native';

const styles = StyleSheet.create({
    container: {
        flexDirection: 'row',
        backgroundColor: '#222'
    },
    image: {
        width: Dimensions.get('window').width
    },
    buttons: {
        height: 15,
        marginTop: -15,
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        flexDirection: 'row'
    },
    button: {
        margin: 3,
        width: 8,
        height: 8,
        borderRadius: 8 / 2,
        backgroundColor: '#ccc',
        opacity: 0.9
    },
    buttonSelected: {
        opacity: 1,
        backgroundColor: '#fff',
    }
});

export default class ImageSlider extends Component {
    constructor(props) {
        super(props);

        this.state = {
            position: 0,
            height: Dimensions.get('window').width * (4 / 9),
            left: new Animated.Value(0)
        };
    }

    _move(index) {
        let width = Dimensions.get('window').width;
        Animated.spring(this.state.left, {toValue: index * -width, friction: 10, tension: 50}).start();
        this.setState({position: index});
    }

    componentWillMount() {
        let release = (e, gestureState) => {
            let width = Dimensions.get('window').width;
            let relativeDistance = gestureState.dx / width;
            let vx = gestureState.vx;
            let change = 0;

            if (relativeDistance < -0.5 || (relativeDistance < 0 && vx <= 0.5)) {
                change = 1;
            } else if (relativeDistance > 0.5 || (relativeDistance > 0 && vx >= 0.5)) {
                change = -1;
            }
            if (this.state.position === 0 && change === -1) {
                change = 0;
            } else if (this.state.position + change >= this.props.images.length) {
                change = (this.props.images.length) - (this.state.position + change);
            }
            this._move(this.state.position + change);
        };

        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => {
                return true;
            },
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
            onPanResponderRelease: release,
            onPanResponderTerminate: release,
            onPanResponderMove: (e, gestureState) => {
                let dx = gestureState.dx;
                let width = Dimensions.get('window').width;
                this.state.left.setValue(-(this.state.position * width) + Math.round(dx));
            }
        });
    }

    render() {
        let width = Dimensions.get('window').width;
        let height = this.props.height || this.state.height;
        return (<View>
            <Animated.View
                style={[styles.container, {height: height, width: width * this.props.images.length, transform: [{translateX: this.state.left}]}]}
                {...this._panResponder.panHandlers}>
                    {this.props.images.map((image, index) => {
                        return (<Image key={index} source={{uri: image}} style={[styles.image, {height: height}]}/>)
                    })}
            </Animated.View>
            <View style={styles.buttons}>
                {this.props.images.map((image, index) => {
                    return (<TouchableHighlight
                        key={index}
                        underlayColor="#ccc"
                        onPress={() => this._move(index)}
                        style={[styles.button, this.state.position === index && styles.buttonSelected]}>
                            <View></View>
                    </TouchableHighlight>);
                })}
            </View>
        </View>);
    }
}
<|endoftext|>


<commit_before>const path = require('path')
const react = require('@neutrinojs/react')
const copy = require('@neutrinojs/copy')
const jest = require('@neutrinojs/jest')
const wext = require('neutrino-webextension')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const MomentLocalesPlugin = require('moment-locales-webpack-plugin')
const argv = require('yargs').argv

module.exports = {
  options: {
    mains: {
      content: {
        entry: 'content',
        webext: {
          type: 'content_scripts',
          manifest: {
            matches: ['<all_urls>']
          },
          setup: 'content/__fake__/env.ts'
        }
      },

      selection: {
        entry: 'selection',
        webext: {
          type: 'content_scripts',
          manifest: {
            match_about_blank: true,
            all_frames: true,
            matches: ['<all_urls>']
          }
        }
      },

      popup: {
        entry: 'popup',
        webext: {
          type: 'browser_action',
          manifest: {
            default_icon: {
              '19': 'assets/icon-19.png',
              '38': 'assets/icon-38.png'
            }
          },
          setup: 'popup/__fake__/env.ts'
        }
      },

      options: {
        entry: 'options',
        webext: {
          type: 'options_ui',
          manifest: {
            open_in_tab: true
          },
          setup: 'options/__fake__/env.ts'
        }
      },

      background: {
        entry: 'background',
        webext: {
          type: 'background',
          setup: 'background/__fake__/env.ts'
        }
      },

      notebook: {
        entry: 'notebook'
      },

      history: {
        entry: 'history'
      },

      'quick-search': {
        entry: 'quick-search'
      },

      'audio-control': {
        entry: 'audio-control'
      }
    }
  },
  use: [
    react({
      style: {
        test: /\.(css|scss)$/,
        modulesTest: /\.module\.(css|scss)$/,
        loaders: [
          // Define loaders as objects. Note: loaders must be specified in reverse order.
          // ie: for the loaders below the actual execution order would be:
          // input file -> sass-loader -> postcss-loader -> css-loader -> style-loader/mini-css-extract-plugin
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require('autoprefixer')]
            },
            useId: 'postcss'
          },
          {
            loader: 'sass-loader',
            useId: 'scss'
          }
        ]
      },
      babel: {
        presets: [
          [
            '@babel/preset-env',
            {
              /* remove targets set by neutrino web preset preferring browserslistrc */
            }
          ],
          [
            '@babel/preset-typescript',
            {
              isTSX: true,
              allExtensions: true
            }
          ]
        ],
        plugins: [
          [
            'import',
            {
              libraryName: 'antd'
            }
          ]
        ]
      }
    }),
    copy({
      patterns: [
        { context: 'assets', from: '**/*', to: 'assets', toType: 'dir' },
        {
          context: 'src/_locales/manifest',
          from: '**/*',
          to: '_locales',
          toType: 'dir'
        }
      ]
    }),
    neutrino => {
      /* eslint-disable indent */

      // avoid collision
      neutrino.config.output.jsonpFunction('saladictJSONP')

      // transform *.shadow.(css|scss) to string
      // this will be injected into shadow-dom style tag
      // prettier-ignore
      const shadowStyleRules = neutrino.config.module
        .rule('style')
          .oneOf('shadow')
            .before('normal')
            .test(/\.shadow\.(css|scss)$/)
              .use('tostring')
                .loader('to-string-loader')
                .end()
              .use('minify')
              .after('css')
                .loader('clean-css-loader')
                .options({
                  level: 1,
                })
                .end()
      // copy loaders from normal to shadow
      // prettier-ignore
      neutrino.config.module
        .rule('style')
          .oneOf('normal')
            .uses.values()
              .filter(rule => !/^(extract|style)$/.test(rule.name))
              .forEach(rule => {
                shadowStyleRules
                  .use(rule.name)
                    .loader(rule.get('loader'))
                    .options(rule.get('options'))
              })

      // prettier-ignore
      neutrino.config
        .module
          .rule('compile') // add ts extensions for babel ect
            .test(/\.(mjs|jsx|js|ts|tsx)$/)
            .end()
          .end()
        .resolve
          .extensions // typescript extensions
            .add('.ts')
            .add('.tsx')
            .end()
          .alias // '@' src alias
            .set('@', path.join(__dirname, 'src'))
            .end()
          .end()
      /* eslint-enable indent */

      if (argv.mode === 'production') {
        // prettier-ignore
        neutrino.config
          .performance
            .hints(false)
            .end()
          .plugin('momentjs')
            .use(MomentLocalesPlugin, [{ localesToKeep: ['zh-cn', 'zh-tw'] }])
            .end()
          .optimization
            .merge({
              splitChunks: {
                cacheGroups: {
                  react: {
                    test: /[\\/]node_modules[\\/](react|i18next)/,
                    name: 'react',
                    chunks: 'all'
                  },
                  dictpanel: {
                    test: /[\\/](src[\\/]content)|(components[\\/]dictionaries)[\\/]/,
                    name: 'dictpanel',
                    chunks: 'all'
                  },
                  antd: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'antd',
                    chunks: ({ name }) => /^(notebook|options|history)$/.test(name),
                    reuseExistingChunk: true
                  }
                }
              },
            })
      }

      if (argv.analyze || argv.analyse) {
        // prettier-ignore
        neutrino.config
          .plugin('bundle-analyze')
          .use(BundleAnalyzerPlugin);
      }
    },
    jest({
      testRegex: ['test/specs/.*\\.spec\\.(ts|tsx|js|jsx)'],
      setupFilesAfterEnv: ['<rootDir>/config/jest/setupTests.js'],
      moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1'
      },
      transform: {
        '\\.(mjs|jsx|js|ts|tsx)$': require.resolve(
          '@neutrinojs/jest/src/transformer'
        )
      }
    }),
    wext({
      polyfill: true
    })
  ]
}
<commit_msg>chore: split chunks<commit_after>const path = require('path')
const react = require('@neutrinojs/react')
const copy = require('@neutrinojs/copy')
const jest = require('@neutrinojs/jest')
const wext = require('neutrino-webextension')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const MomentLocalesPlugin = require('moment-locales-webpack-plugin')
const argv = require('yargs').argv

module.exports = {
  options: {
    mains: {
      content: {
        entry: 'content',
        webext: {
          type: 'content_scripts',
          manifest: {
            matches: ['<all_urls>']
          },
          setup: 'content/__fake__/env.ts'
        }
      },

      selection: {
        entry: 'selection',
        webext: {
          type: 'content_scripts',
          manifest: {
            match_about_blank: true,
            all_frames: true,
            matches: ['<all_urls>']
          }
        }
      },

      popup: {
        entry: 'popup',
        webext: {
          type: 'browser_action',
          manifest: {
            default_icon: {
              '19': 'assets/icon-19.png',
              '38': 'assets/icon-38.png'
            }
          },
          setup: 'popup/__fake__/env.ts'
        }
      },

      options: {
        entry: 'options',
        webext: {
          type: 'options_ui',
          manifest: {
            open_in_tab: true
          },
          setup: 'options/__fake__/env.ts'
        }
      },

      background: {
        entry: 'background',
        webext: {
          type: 'background',
          setup: 'background/__fake__/env.ts'
        }
      },

      notebook: {
        entry: 'notebook'
      },

      history: {
        entry: 'history'
      },

      'quick-search': {
        entry: 'quick-search'
      },

      'audio-control': {
        entry: 'audio-control'
      }
    }
  },
  use: [
    react({
      style: {
        test: /\.(css|scss)$/,
        modulesTest: /\.module\.(css|scss)$/,
        loaders: [
          // Define loaders as objects. Note: loaders must be specified in reverse order.
          // ie: for the loaders below the actual execution order would be:
          // input file -> sass-loader -> postcss-loader -> css-loader -> style-loader/mini-css-extract-plugin
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require('autoprefixer')]
            },
            useId: 'postcss'
          },
          {
            loader: 'sass-loader',
            useId: 'scss'
          }
        ]
      },
      babel: {
        presets: [
          [
            '@babel/preset-env',
            {
              /* remove targets set by neutrino web preset preferring browserslistrc */
            }
          ],
          [
            '@babel/preset-typescript',
            {
              isTSX: true,
              allExtensions: true
            }
          ]
        ],
        plugins: [
          [
            'import',
            {
              libraryName: 'antd'
            }
          ]
        ]
      }
    }),
    copy({
      patterns: [
        { context: 'assets', from: '**/*', to: 'assets', toType: 'dir' },
        {
          context: 'src/_locales/manifest',
          from: '**/*',
          to: '_locales',
          toType: 'dir'
        }
      ]
    }),
    neutrino => {
      /* eslint-disable indent */

      // avoid collision
      neutrino.config.output.jsonpFunction('saladictJSONP')

      // transform *.shadow.(css|scss) to string
      // this will be injected into shadow-dom style tag
      // prettier-ignore
      const shadowStyleRules = neutrino.config.module
        .rule('style')
          .oneOf('shadow')
            .before('normal')
            .test(/\.shadow\.(css|scss)$/)
              .use('tostring')
                .loader('to-string-loader')
                .end()
              .use('minify')
              .after('css')
                .loader('clean-css-loader')
                .options({
                  level: 1,
                })
                .end()
      // copy loaders from normal to shadow
      // prettier-ignore
      neutrino.config.module
        .rule('style')
          .oneOf('normal')
            .uses.values()
              .filter(rule => !/^(extract|style)$/.test(rule.name))
              .forEach(rule => {
                shadowStyleRules
                  .use(rule.name)
                    .loader(rule.get('loader'))
                    .options(rule.get('options'))
              })

      // prettier-ignore
      neutrino.config
        .module
          .rule('compile') // add ts extensions for babel ect
            .test(/\.(mjs|jsx|js|ts|tsx)$/)
            .end()
          .end()
        .resolve
          .extensions // typescript extensions
            .add('.ts')
            .add('.tsx')
            .end()
          .alias // '@' src alias
            .set('@', path.join(__dirname, 'src'))
            .end()
          .end()
      /* eslint-enable indent */

      if (argv.mode === 'production') {
        // prettier-ignore
        neutrino.config
          .performance
            .hints(false)
            .end()
          .plugin('momentjs')
            .use(MomentLocalesPlugin, [{ localesToKeep: ['zh-cn', 'zh-tw'] }])
            .end()
          .optimization
            .merge({
              splitChunks: {
                cacheGroups: {
                  dictpanel: {
                    test: /([\\/]src[\\/]content[\\/])|([\\/]components[\\/]dictionaries[\\/])|([\\/]node_modules[\\/]react)/,
                    name: 'dictpanel',
                    chunks: ({ name }) => !/^(selection|audio-control|background)$/.test(name)
                  },
                  antd: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'antd',
                    chunks: ({ name }) => /^(notebook|options|history)$/.test(name),
                    reuseExistingChunk: true
                  }
                }
              },
            })
      }

      if (argv.analyze || argv.analyse) {
        // prettier-ignore
        neutrino.config
          .plugin('bundle-analyze')
          .use(BundleAnalyzerPlugin);
      }
    },
    jest({
      testRegex: ['test/specs/.*\\.spec\\.(ts|tsx|js|jsx)'],
      setupFilesAfterEnv: ['<rootDir>/config/jest/setupTests.js'],
      moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1'
      },
      transform: {
        '\\.(mjs|jsx|js|ts|tsx)$': require.resolve(
          '@neutrinojs/jest/src/transformer'
        )
      }
    }),
    wext({
      polyfill: true
    })
  ]
}
<|endoftext|>


<commit_before>//Document all the things
const fs = require("fs")
const gen = require("../lib")
const todo = [
  ["zeronet", "zeronet.js", "./README.md"]
]
require("colors")
const fix = {
  "zeronet-client": true,
  "zeronet-crypto": true,
  "zeronet-fileserver": true,
  "zeronet-swarm": true
}
const read = f => fs.readFileSync(f).toString().split("\n")
let add
if (process.env.ONLY)
  add = process.env.ONLY.split(",")
else
  add = fs.readdirSync(".")
  .filter(s => !!s.match(/^zeronet-[a-z]+$/))

add.map(s => todo.push([s, s + (fix[s] ? "/*.js" : "/lib/**/*.js"), s + "/README.md"]))

function next() {
  const i = todo.shift()
  if (!i) process.exit(console.log("DONE".green.bold))
  try {
    console.log(" => %s".blue.bold, i[0])
    gen(i[0], i[1], {
      footer: read(".github/FOOTER.md")
    }, (err, res) => {
      if (err) {
        console.error("FAILED TO GENERATE DOCS FOR %s".red.bold, i[0])
        console.error(err)
      } else {
        console.log("   => OK %s".green.bold, i[0])
        let cut
        let r = read(i[2]).filter(l => {
          if (!cut)
            if (l == "# API" || l == "-----") cut = true
          return !cut
        })
        fs.writeFileSync(i[2], new Buffer(r.concat(res.split("\n")).join("\n")))
      }
      next()
    })
  } catch (err) {
    console.error("FAILED TO GENERATE DOCS FOR %s", i[0])
    console.error(err)
    next()
  }
}

next()
<commit_msg>Doc fix<commit_after>//Document all the things
const fs = require("fs")
const gen = require("../lib")
const todo = [
  ["zeronet", "zeronet.js", "./README.md"]
]
require("colors")
const fix = {
  "zeronet-crypto": true,
  "zeronet-fileserver": true,
  "zeronet-swarm": true
}
const read = f => fs.readFileSync(f).toString().split("\n")
let add
if (process.env.ONLY)
  add = process.env.ONLY.split(",")
else
  add = fs.readdirSync(".")
  .filter(s => !!s.match(/^zeronet-[a-z]+$/))

add.map(s => todo.push([s, s + (fix[s] ? "/*.js" : "/lib/**/*.js"), s + "/README.md"]))

function next() {
  const i = todo.shift()
  if (!i) process.exit(console.log("DONE".green.bold))
  try {
    console.log(" => %s".blue.bold, i[0])
    gen(i[0], i[1], {
      footer: read(".github/FOOTER.md")
    }, (err, res) => {
      if (err) {
        console.error("FAILED TO GENERATE DOCS FOR %s".red.bold, i[0])
        console.error(err)
      } else {
        console.log("   => OK %s".green.bold, i[0])
        let cut
        let r = read(i[2]).filter(l => {
          if (!cut)
            if (l == "# API" || l == "-----") cut = true
          return !cut
        })
        fs.writeFileSync(i[2], new Buffer(r.concat(res.split("\n")).join("\n")))
      }
      next()
    })
  } catch (err) {
    console.error("FAILED TO GENERATE DOCS FOR %s", i[0])
    console.error(err)
    next()
  }
}

next()
<|endoftext|>


<commit_before>
/**
 * Takes the contents of a file and generates a statistics
 * object with information about the lines.
*/
exports = module.exports = function(contents, options, callback) {
	if(!callback && typeof options === 'function') {
		callback = options;
		options = {};
	}

	// Set defauls if no options are passed
	if(!options.singleLineComment) options.singleLineComment = '//';
	if(!options.blockCommentOpen) options.blockCommentOpen = '/*';
	if(!options.blockCommentClose) options.blockCommentClose = '*/';

	var statistics = {
		'total': 0,
		'source': 0,
		'singleLineComments': 0,
		'blockComments': 0,
		'empty': 0
	};

	var lines = contents.split(options.lineSeparator || '\r\n');
	var line, blockCommentOpen;
	for(var i = 0; i < lines.length; i++) {
		statistics.total++;

		line = lines[i].trim();
		switch(lineType(line, options)) {
			case 'empty':
				statistics.empty++;
			break;
			case 'singleLineComment':
				statistics.singleLineComments++;
			break;
			case 'blockCommentOpen':
				blockCommentOpen = true;
			break;
			case 'blockCommentClose':
				if(blockCommentOpen) {
					statistics.blockComments++;
					blockCommentOpen = false;
				}
			break;
			case 'source':
				if(!blockCommentOpen) {
					statistics.source++;
				}
			break;
		}
	}
	if(typeof callback == 'function')
		return callback(null, statistics);
	return statistics;

};

/**
 * Method that determines when kind of line we have
*/
var lineType = function(line, options) {
	if(line.length === 0) return 'empty';
	if(line.substr(0, 2) == options.singleLineComment) return 'singleLineComment';
	if(line.substr(0, 2) == options.blockCommentOpen) return 'blockCommentOpen';
	if(line.substr(line.length - 2) == options.blockCommentClose) return 'blockCommentClose';
	return 'source';
};
<commit_msg>Improve options handling<commit_after>
/**
 * Takes the contents of a file and generates a statistics
 * object with information about the lines.
*/
exports = module.exports = function(contents, options, callback) {
	if(!callback && typeof options === 'function') {
		callback = options;
		options = {};
	}
	if(!options) options = {};

	// Set defauls if no options are passed
	if(!options.singleLineComment) options.singleLineComment = '//';
	if(!options.blockCommentOpen) options.blockCommentOpen = '/*';
	if(!options.blockCommentClose) options.blockCommentClose = '*/';

	var statistics = {
		'total': 0,
		'source': 0,
		'singleLineComments': 0,
		'blockComments': 0,
		'empty': 0
	};

	var lines = contents.split(options.lineSeparator || '\r\n');
	var line, blockCommentOpen;
	for(var i = 0; i < lines.length; i++) {
		statistics.total++;

		line = lines[i].trim();
		switch(lineType(line, options)) {
			case 'empty':
				statistics.empty++;
			break;
			case 'singleLineComment':
				statistics.singleLineComments++;
			break;
			case 'blockCommentOpen':
				blockCommentOpen = true;
			break;
			case 'blockCommentClose':
				if(blockCommentOpen) {
					statistics.blockComments++;
					blockCommentOpen = false;
				}
			break;
			case 'source':
				if(!blockCommentOpen) {
					statistics.source++;
				}
			break;
		}
	}
	if(typeof callback == 'function')
		return callback(null, statistics);
	return statistics;

};

/**
 * Method that determines when kind of line we have
*/
var lineType = function(line, options) {
	if(line.length === 0) return 'empty';
	if(line.substr(0, 2) == options.singleLineComment) return 'singleLineComment';
	if(line.substr(0, 2) == options.blockCommentOpen) return 'blockCommentOpen';
	if(line.substr(line.length - 2) == options.blockCommentClose) return 'blockCommentClose';
	return 'source';
};
<|endoftext|>


<commit_before>import EmberObject, { computed } from '@ember/object';
import { readOnly } from '@ember/object/computed';
import Ember from 'ember';

import ol from 'openlayers';

import computedPoint from '../computed/computed-point';
import safeComputed from '../computed/safe-computed';
import geographicDistance from '../utils/geo-distance';
import getNextSmallerIndex from '../utils/next-smaller-index';

let Fix = EmberObject.extend({
  fixCalc: null,

  _t: readOnly('fixCalc.time'),

  t: computed('_t', 'flight.{startTime,endTime}', function () {
    let _t = this._t;
    if (_t === -1) {
      return this.get('flight.endTime');
    } else if (_t >= this.get('flight.startTime') && _t <= this.get('flight.endTime')) {
      return _t;
    }
  }),

  _index: safeComputed('t', 'flight.time', (t, time) => getNextSmallerIndex(time, t)),

  t_prev: safeComputed('_index', 'flight.time', (index, time) => time[index]),
  t_next: safeComputed('_index', 'flight.time', (index, time) => time[index + 1]),

  time: readOnly('t_prev'),

  coordinate: safeComputed('t', 'flight.geometry', (time, geometry) => geometry.getCoordinateAtM(time)),

  lon: readOnly('coordinate.0'),
  lat: readOnly('coordinate.1'),

  'alt-msl': safeComputed('coordinate.2', 'flight.geoid', (altitude, geoid) => altitude - geoid),

  'alt-gnd': safeComputed('alt-msl', 'elevation', (altitude, elevation) => {
    let value = altitude - elevation;
    return value >= 0 ? value : 0;
  }),

  point: computedPoint('coordinate'),
  pointXY: computedPoint('coordinate', 'XY'),

  heading: safeComputed('_coordinate_prev', '_coordinate_next', (prev, next) =>
    Math.atan2(next[0] - prev[0], next[1] - prev[1]),
  ),

  vario: safeComputed('_coordinate_prev.2', '_coordinate_next.2', '_dt', (prev, next, dt) => (next - prev) / dt),

  speed: safeComputed('_coordinate_prev', '_coordinate_next', '_dt', (prev, next, dt) => {
    let loc_prev = ol.proj.transform(prev, 'EPSG:3857', 'EPSG:4326');
    let loc_next = ol.proj.transform(next, 'EPSG:3857', 'EPSG:4326');

    return geographicDistance(loc_next, loc_prev) / dt;
  }),

  _dt: safeComputed('t_prev', 't_next', (prev, next) => next - prev),

  _coordinate_prev: safeComputed('t_prev', 'flight.geometry', (time, geometry) => geometry.getCoordinateAtM(time)),
  _coordinate_next: safeComputed('t_next', 'flight.geometry', (time, geometry) => geometry.getCoordinateAtM(time)),

  elevation: safeComputed('flight.elev_h', '_elev_index', (elev_h, index) => elev_h[index]),

  _elev_index: safeComputed('flight.elev_t', 't', (elev_t, t) => getNextSmallerIndex(elev_t, t)),
});

Fix[Ember.NAME_KEY] = 'Fix';

export default Fix;
<commit_msg>utils/fix: Convert to ES6 class<commit_after>import EmberObject, { computed } from '@ember/object';
import { readOnly } from '@ember/object/computed';

import ol from 'openlayers';

import computedPoint from '../computed/computed-point';
import safeComputed from '../computed/safe-computed';
import geographicDistance from '../utils/geo-distance';
import getNextSmallerIndex from '../utils/next-smaller-index';

export default class Fix extends EmberObject {
  @readOnly('fixCalc.time') _t;

  @computed('_t', 'flight.{startTime,endTime}')
  get t() {
    let _t = this._t;
    if (_t === -1) {
      return this.get('flight.endTime');
    } else if (_t >= this.get('flight.startTime') && _t <= this.get('flight.endTime')) {
      return _t;
    }
  }

  @safeComputed('t', 'flight.time', (t, time) => getNextSmallerIndex(time, t)) _index;

  @safeComputed('_index', 'flight.time', (index, time) => time[index]) t_prev;
  @safeComputed('_index', 'flight.time', (index, time) => time[index + 1]) t_next;

  @readOnly('t_prev') time;

  @safeComputed('t', 'flight.geometry', (time, geometry) => geometry.getCoordinateAtM(time)) coordinate;

  @readOnly('coordinate.0') lon;
  @readOnly('coordinate.1') lat;

  @safeComputed('coordinate.2', 'flight.geoid', (altitude, geoid) => altitude - geoid) 'alt-msl';

  @safeComputed('alt-msl', 'elevation', (altitude, elevation) => {
    let value = altitude - elevation;
    return value >= 0 ? value : 0;
  })
  'alt-gnd';

  @computedPoint('coordinate') point;
  @computedPoint('coordinate', 'XY') pointXY;

  @safeComputed('_coordinate_prev', '_coordinate_next', (prev, next) =>
    Math.atan2(next[0] - prev[0], next[1] - prev[1]),
  )
  heading;

  @safeComputed('_coordinate_prev.2', '_coordinate_next.2', '_dt', (prev, next, dt) => (next - prev) / dt) vario;

  @safeComputed('_coordinate_prev', '_coordinate_next', '_dt', (prev, next, dt) => {
    let loc_prev = ol.proj.transform(prev, 'EPSG:3857', 'EPSG:4326');
    let loc_next = ol.proj.transform(next, 'EPSG:3857', 'EPSG:4326');

    return geographicDistance(loc_next, loc_prev) / dt;
  })
  speed;

  @safeComputed('t_prev', 't_next', (prev, next) => next - prev) _dt;

  @safeComputed('t_prev', 'flight.geometry', (time, geometry) => geometry.getCoordinateAtM(time)) _coordinate_prev;
  @safeComputed('t_next', 'flight.geometry', (time, geometry) => geometry.getCoordinateAtM(time)) _coordinate_next;

  @safeComputed('flight.elev_h', '_elev_index', (elev_h, index) => elev_h[index]) elevation;

  @safeComputed('flight.elev_t', 't', (elev_t, t) => getNextSmallerIndex(elev_t, t)) _elev_index;
}
<|endoftext|>


<commit_before><commit_msg>Create Vue hello-world for navigation<commit_after>'use strict';

var Vue = require('vue');

window.onload = function(){
    new Vue({
      el: '#navigation-module',
      data: {
        message: 'Hello Vue.js!'
      }
    });
};
<|endoftext|>


<commit_before>
// Vendor Modules
/////////////////////////
var expect = require('expect.js')

// Our Modules
/////////////////////////
var storageManger = require('../')

//var config = require('c:/temp/config.js')

// Tests
/////////////////////////
describe('storageManger: localStorage', function()
{

it('should load', function(done)
{
	var googleStorage = storageManger.init('google', "lala", done)
})

// end of test suite
})<commit_msg>small fix<commit_after>
// Vendor Modules
/////////////////////////
var expect = require('expect.js')

// Our Modules
/////////////////////////
var storageManager = require('../')

//var config = require('c:/temp/config.js')

// Tests
/////////////////////////
describe('storageManger: googleStorage', function()
{

it('should load', function(done)
{
	console.log(storageManager)
	var googleStorage = storageManger.start('google', "lala", done)
})

// end of test suite
})<|endoftext|>


<commit_before>camCatcher = {
  init: function (videoElem) {
    var self = this
    self.vidoElem = videoElem
    navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia ||navigator.mozGetUserMedia || navigator.msGetUserMedia)
    if(navigator.getUserMedia) {
      navigator.getUserMedia({
        video: true
      , audio: false
      }
    , function (localMediaStream) {
        if(localMediaStream) {
          self.vidoElem.src = URL.createObjectURL(localMediaStream)
          self.vidoElem.play()
        }
      }
    , function (err) {
        console.log(err)
      })
    }
  }
, catchFrame: function () {
    var self = this
    if(!self.vidoElem.src) return false
    var shotImage = self.captureBase64FromFromVideoElem(self.vidoElem, 400, 400)
  , shotImageSplit, shotImageType, shotImageBase64

    shotImageSplit = shotImage.split(';')
    shotImageType = shotImageSplit[0].split('image/')[1]
    shotImageBase64 = shotImageSplit[1].split('base64,')[1]
    return {
      base64: shotImageBase64
    , type: shotImageType
    }
  }
, captureBase64FromFromVideoElem: function (videoElement, height, width) {
    var self = this
  , thisCanvas = document.createElement('canvas')
    thisCanvas.getContext('2d').drawImage(videoElement, 0, 0, width, height)
    return thisCanvas.toDataURL()
  }
}

Template.blockCamCatcher.rendered = function () {
  var self = this
  camCatcher.init(self.find('.camCatcher__video'))
}

Template.blockCamCatcher.events({
  'click .camCatcher__trigger': function () {
    if(camCatcher.callback) {
      camCatcher.callback(camCatcher.catchFrame())
    } else {
      console.log('No Callback provided')
      console.log(camCatcher.catchFrame())
    }
  }
})<commit_msg>Messages for when no camera is found or access isn't beeing allowed<commit_after>camCatcher = {
  init: function (videoElem) {
    var self = this
    self.vidoElem = videoElem
    navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia ||navigator.mozGetUserMedia || navigator.msGetUserMedia)
    if(navigator.getUserMedia) {
      navigator.getUserMedia({
        video: true
      , audio: false
      }
    , function (localMediaStream) {
        if(localMediaStream) {
          self.vidoElem.src = URL.createObjectURL(localMediaStream)
          self.vidoElem.play()
        }
      }
    , function (err) {
        console.log(err)
      })
      setTimeout(function () {
        if(self.vidoElem.src) return true
        var failMessage = Object.create(notification)
        failMessage.type = 'warning'
        failMessage.text = 'You have to allow camera access to be able to upload images'
        failMessage.show()
      }, 3000)
    } else {
      var failMessage = Object.create(notification)
      failMessage.type = 'warning'
      failMessage.text = 'No camera found'
      failMessage.show()
    }
  }
, catchFrame: function () {
    var self = this
    if(!self.vidoElem.src) return false
    var shotImage = self.captureBase64FromFromVideoElem(self.vidoElem, 400, 400)
  , shotImageSplit, shotImageType, shotImageBase64

    shotImageSplit = shotImage.split(';')
    shotImageType = shotImageSplit[0].split('image/')[1]
    shotImageBase64 = shotImageSplit[1].split('base64,')[1]
    return {
      base64: shotImageBase64
    , type: shotImageType
    }
  }
, captureBase64FromFromVideoElem: function (videoElement, height, width) {
    var self = this
  , thisCanvas = document.createElement('canvas')
    thisCanvas.getContext('2d').drawImage(videoElement, 0, 0, width, height)
    return thisCanvas.toDataURL()
  }
}

Template.blockCamCatcher.rendered = function () {
  var self = this
  camCatcher.init(self.find('.camCatcher__video'))
}

Template.blockCamCatcher.events({
  'click .camCatcher__trigger': function () {
    if(camCatcher.callback) {
      camCatcher.callback(camCatcher.catchFrame())
    } else {
      console.log('No Callback provided')
      console.log(camCatcher.catchFrame())
    }
  }
})<|endoftext|>


<commit_before>import React from 'react';
import {
  View,
  FlatList
} from 'react-native';

import RoleItem from './RoleItem';
import container from './CaretakerRolesList.container';
import styles from './CaretakerRolesList.styles';

class CaretakerRolesList extends React.Component {
  _renderItem = ({item}) => <RoleItem
    name={item.name}
    description={item.description}
    isHelper={this.props.isHelper}
    hasRole={this.props.userHasRole(item.name)}
  />;

  render() {
    return <View>
      <FlatList
        data={this.props.caretakerRoles}
        dataWhichCouldChange={this.props.caretakerRoles.map(role => role.volunteer)}
        renderItem={this._renderItem}
        keyExtractor={(item) => item.id}
      />
    </View>;
  }
}

export default container(CaretakerRolesList);
<commit_msg>Added prop types fo roles list<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import {
  View,
  FlatList
} from 'react-native';

import RoleItem from './RoleItem';
import container from './CaretakerRolesList.container';
import styles from './CaretakerRolesList.styles';

class CaretakerRolesList extends React.Component {
  _renderItem = ({item}) => <RoleItem
    name={item.name}
    description={item.description}
    isHelper={this.props.isHelper}
    hasRole={this.props.userHasRole(item.name)}
  />;

  render() {
    return <View>
      <FlatList
        data={this.props.caretakerRoles}
        dataWhichCouldChange={this.props.caretakerRoles.map(role => role.volunteer)}
        renderItem={this._renderItem}
        keyExtractor={(item) => item.id}
      />
    </View>;
  }
}

CaretakerRolesList.propTypes = {
//From container
  caretakerRoles: PropTypes.array,
  isHelper: PropTypes.bool,
  userHasRole: PropTypes.func
};

export default container(CaretakerRolesList);
<|endoftext|>


<commit_before>/*
Copyright (c) 2016, Geomatics and Cartographic Research Centre, Carleton
University
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 - Neither the name of the Geomatics and Cartographic Research Centre,
   Carleton University nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

;(function($,$n2){
"use strict";

// Localization
var _loc = function(str,args){ return $n2.loc(str,'nunaliit2-couch',args); },
DH = 'n2.comment';

//===================================================================================

var CommentStreamDisplay = $n2.Class({

	commentSchema: null,

	dispatchService: null,

	documentSource: null,

	showService: null,

	createDocProcess: null,

	initialize: function(opts_){

		var opts = $n2.extend({
			schema: null
			,dispatchService: null
			,documentSource: null
			,showService: null
			,createDocProcess: null
		},opts_);

		var _this = this;

		this.commentSchema = opts.schema;
		this.dispatchService = opts.dispatchService;
		this.documentSource = opts.documentSource;
		this.showService = opts.showService;
		this.createDocProcess = opts.createDocProcess;

		if( this.dispatchService ){
			var f = function(msg, addr, dispatcher){
				_this._handleDispatch(msg, addr, dispatcher);
			};
			this.dispatchService.register(DH, 'documentContentCreated', f);
			this.dispatchService.register(DH, 'documentContentUpdated', f);
			this.dispatchService.register(DH, 'documentDeleted', f);
		};
	},

	display: function(opts_){
		var opts = $n2.extend({
			divId: null
			,div: null
			,doc: null
		},opts_);

		var _this = this;

		var doc = opts.doc;
		var docId = doc._id;
		var showService = this.showService;

		var buttonDisplay = new $n2.couchDisplay.ButtonDisplay();

		var $elem = opts.div;
		if( ! $elem ) {
			$elem = $('#'+opts.divId);
		};
		if( ! $elem.length) {
			return;
		};
		if( !showService ) {
			$n2.log('Show service not available for comment process');
			return;
		};

		$elem.empty();

		// Insert comment stream
		$('<div>')
			.addClass('n2Comment_stream n2Comment_stream_'+$n2.utils.stringToHtmlId(docId))
			.appendTo($elem);
		this._refreshStream({
			docId: docId
		});

		// Insert 'Add Comment' section
		var $addSection = $('<div>')
			.addClass('n2Comment_add_section')
			.appendTo($elem);
		this._resetAddSection(doc, $addSection);
	},

	_refreshStream: function(opts_){
		var opts = $n2.extend({
			docId: null
		},opts_);

		var _this = this;

		var originId = opts.docId;
		var buttonDisplay = new $n2.couchDisplay.ButtonDisplay();

		// Get references
		this.documentSource.getReferencesFromOrigin({
			docId: originId
			,onSuccess: loadedDocIds
		});

		function loadedDocIds(refDocIds){
			// Get documents that include comments
			_this.documentSource.getDocumentInfoFromIds({
				docIds: refDocIds
				,onSuccess: loadedDocInfos
			});
		};

		function loadedDocInfos(docInfos){
			// Sort comments by last updated time
			docInfos.sort(function(a,b){
				var aTime = a.updatedTime;
				if( !aTime ){
					aTime = a.createdTime;
				};

				var bTime = b.updatedTime;
				if( !bTime ){
					bTime = b.createdTime;
				};

				if( aTime && bTime ){
					return bTime - aTime;
				};
				if( aTime ) return -1;
				if( bTime ) return 1;

				if( a.id > b.id ) {
					return 1;
				}
				return -1;
			});

			// Display comments
			var $stream = $('.n2Comment_stream_'+$n2.utils.stringToHtmlId(originId));
			if( $stream.length > 0 ){
				$stream.empty();
				docInfos.forEach(function(docInfo){
					var docId = docInfo.id;
					var $commentDiv = $('<div>')
						.addClass('n2Comment_doc n2Comment_doc_'+$n2.utils.stringToHtmlId(docId))
						.attr('n2DocId',docId)
						.appendTo($stream);
					var $content = $('<div>')
						.addClass('n2Comment_content')
						.appendTo($commentDiv);
					_this.showService.printDocument($content, docId);

					var $buttons = $('<div>')
						.addClass('n2Comment_buttons')
						.appendTo($commentDiv);

					buttonDisplay.drawButton({
						elem: $buttons
						,name: 'reply'
						,label: _loc('Reply')
						,click: function(){
							var $outer = $(this).parents('.n2Comment_doc');
							var docId = $outer.attr('n2DocId');
							_this._addReply(docId,$outer);
						}
					});

					buttonDisplay.drawButton({
						elem: $buttons
						,name: 'more_details'
						,label: _loc('More Details')
						,click: function(){
							var docId = $(this).parents('.n2Comment_doc').attr('n2DocId');
							_this._changeFocus(docId);
						}
					});
				});
			};
		};
	},

	_resetAddSection: function(doc, $addSection){
		var _this = this;

		var buttonDisplay = new $n2.couchDisplay.ButtonDisplay();

		$addSection.empty();
		buttonDisplay.drawButton({
			elem: $addSection
			,name: 'add_comment'
			,label: _loc('Add Comment')
			,click: function(){
				var $addSection = $(this).parents('.n2Comment_add_section');
				$addSection.empty();
				_this._addComment(doc,$addSection);
			}
		});

	},

	_addComment: function(doc, elem){
		var _this = this;

		var createRelatedDocProcess = this.createDocProcess;
		createRelatedDocProcess.replyToDocument({
			doc: doc
			,schema: this.commentSchema
			,originDocId: doc._id
			,elem: elem
			,onSuccess: function(docId){
				_this._resetAddSection(doc, elem);
			}
			,onError: function(err){
				_this._resetAddSection(doc, elem);
			}
			,onCancel: function(){
				_this._resetAddSection(doc, elem);
			}
		});
	},

	_addReply: function(docId, $outerDiv){
		var _this = this;
		var documentSource = this.documentSource;
		var createRelatedDocProcess = this.createDocProcess;

		var $elem = undefined;
		if( $outerDiv.length > 0 ){
			$elem = $('<div>')
				.addClass('n2Comment_reply')
				.appendTo($outerDiv);
		};

		documentSource.getDocument({
			docId: docId
			,onSuccess: function(doc){
				createRelatedDocProcess.replyToDocument({
					doc: doc
					,schema: _this.commentSchema
					,elem: $elem
				});
			}
		});
	},

	_changeFocus: function(docId){
		var _this = this;

		this.dispatchService.send(DH,{
			type: 'userSelect'
			,docId: docId
		});
	},

	_handleDispatch: function(m, address, dispatcher){
		if( 'documentContentCreated' === m.type ){
			var doc = m.doc;
			this._handleDocumentContent(doc);

		} else if( 'documentContentUpdated' === m.type ){
			var doc = m.doc;
			this._handleDocumentContent(doc);

		} else if( 'documentDeleted' === m.type ){
			var docId = m.docId;
			this._handleDocumentDeleted(docId);
		};
	},

	_handleDocumentContent: function(doc){
		if( doc.nunaliit_origin
		 && typeof doc.nunaliit_origin.doc === 'string' ){
			var originId = doc.nunaliit_origin.doc;
			var $stream = $('.n2Comment_stream_'+$n2.utils.stringToHtmlId(originId));
			if( $stream.length > 0 ){
				// OK, need to refresh this stream
				var divId = $n2.utils.getElementIdentifier($stream);
				this._refreshStream({
					docId: originId
				});
			};
		};
	},

	_handleDocumentDeleted: function(docId){
		if( doc.nunaliit_origin
		 && typeof doc.nunaliit_origin.doc === 'string' ){
			// Remove associated streams
			var $section = $( '.n2Comment_stream_'+$n2.utils.stringToHtmlId(docId) );
			$section.remove();

			// Remove associated sections
			var $section = $( '.n2Comment_doc_'+$n2.utils.stringToHtmlId(docId) );
			$section.remove();
		};
	}
});

//===================================================================================
var CommentService = $n2.Class({

	documentSource: null,

	showService: null,

	createDocProcess: null,

	dispatchService: null,

	customService: null,

	commentSchema: null,

	initialize: function(opts_){
		var opts = $n2.extend({
			documentSource: null
			,showService: null
			,createDocProcess: null
			,dispatchService: null
			,customService: null
			,commentSchema: null
		},opts_);

		this.documentSource = opts.documentSource;
		this.showService = opts.showService;
		this.createDocProcess = opts.createDocProcess;
		this.dispatchService = opts.dispatchService;
		this.customService = opts.customService;
		this.commentSchema = opts.commentSchema;
	},

	setCommentSchema: function(commentSchema){
		this.commentSchema = commentSchema;

		if( this.commentSchema ){
			$('.n2Comment_button_addcomment')
				.removeClass('n2Comment_button_addcomment_unavailable')
				.addClass('n2Comment_button_addcomment_available');
		} else {
			$('.n2Comment_button_addcomment')
				.removeClass('n2Comment_button_addcomment_available')
				.addClass('n2Comment_button_addcomment_unavailable');
		};
	},

	/**
	 * Use this function to add a button to an element. The button
	 * initiates adding a comment associated with the given document.
	 */
	insertAddCommentButton: function(opts_){
		var opts = $n2.extend({
			div: null
			,doc: null
			,buttonDisplay: null
		},opts_);

		var _this = this;

		var doc = opts.doc;
		var $buttons = $(opts.div);

		var buttonDisplay = opts.buttonDisplay;
		if( !buttonDisplay ){
			buttonDisplay = new $n2.couchDisplay.ButtonDisplay();
		};

		var classNames = ['n2Comment_button_addcomment'];
		if( this.commentSchema ){
			classNames.push('n2Comment_button_addcomment_available');
		} else {
			classNames.push('n2Comment_button_addcomment_unavailable');
		};

		// Show 'add comment' button
		buttonDisplay.drawButton({
			elem: $buttons
			,name: 'add_comment'
			,label: _loc('Add Comment')
			,classNames: classNames
			,click: function(){
				_this._addComment(doc);
			}
		});
	},

	_addComment: function(doc){
		var createRelatedDocProcess = this.createDocProcess;
		createRelatedDocProcess.replyToDocument({
			doc: doc
			,schema: this.commentSchema
			,originDocId: doc._id
		});
	},

	getCommentStreamDisplay: function(){
		var commentStreamDisplay = new CommentStreamDisplay({
			schema: this.commentSchema
			,dispatchService: this.dispatchService
			,documentSource: this.documentSource
			,showService: this.showService
			,createDocProcess: this.createDocProcess
		});

		return commentStreamDisplay;
	}
});

//===================================================================================

// Exports
$n2.comment = {
	Service: CommentService
	,CommentStreamDisplay: CommentStreamDisplay
};

})(jQuery,nunaliit2);
<commit_msg>nunaliit2-js: In comment display, retain replies on document updates.<commit_after>/*
Copyright (c) 2016, Geomatics and Cartographic Research Centre, Carleton
University
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 - Neither the name of the Geomatics and Cartographic Research Centre,
   Carleton University nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

;(function($,$n2){
"use strict";

// Localization
var _loc = function(str,args){ return $n2.loc(str,'nunaliit2-couch',args); },
DH = 'n2.comment';

//===================================================================================
/*
 * There should be one instance of this class, per Nunaliit configuration. This is
 * because this component registers events with the dispatcher.
 */

var CommentStreamDisplay = $n2.Class({

	commentService: null,

	dispatchService: null,

	documentSource: null,

	showService: null,

	createDocProcess: null,

	cachedCommentsByDocId: null,

	reinsertElementsId: null,

	initialize: function(opts_){

		var opts = $n2.extend({
			commentService: null
			,dispatchService: null
			,documentSource: null
			,showService: null
			,createDocProcess: null
		},opts_);

		var _this = this;

		this.commentService = opts.commentService;
		this.dispatchService = opts.dispatchService;
		this.documentSource = opts.documentSource;
		this.showService = opts.showService;
		this.createDocProcess = opts.createDocProcess;

		if( this.dispatchService ){
			var f = function(msg, addr, dispatcher){
				_this._handleDispatch(msg, addr, dispatcher);
			};
			this.dispatchService.register(DH, 'documentContentCreated', f);
			this.dispatchService.register(DH, 'documentContentUpdated', f);
			this.dispatchService.register(DH, 'documentDeleted', f);
			this.dispatchService.register(DH, 'cacheRetrieveDocument', f);
		};

		// Create an area to keep elements that should be re-inserted in the
		// comment stream on a redraw.
		var $reinsertElements = $('<div>')
			.css({
				position: 'absolute'
				,display: 'none'
			})
			.addClass('n2comment_reinsert_cache')
			.appendTo( $('body') );
		this.reinsertElementsId = $n2.utils.getElementIdentifier($reinsertElements);
	},

	display: function(opts_){
		var opts = $n2.extend({
			divId: null
			,div: null
			,doc: null
		},opts_);

		var _this = this;

		var doc = opts.doc;
		var docId = doc._id;
		var showService = this.showService;

		var buttonDisplay = new $n2.couchDisplay.ButtonDisplay();

		var $elem = opts.div;
		if( ! $elem ) {
			$elem = $('#'+opts.divId);
		};
		if( ! $elem.length) {
			return;
		};
		if( !showService ) {
			$n2.log('Show service not available for comment process');
			return;
		};

		$elem.empty();

		// Insert comment stream
		$('<div>')
			.addClass('n2Comment_stream n2Comment_stream_'+$n2.utils.stringToHtmlId(docId))
			.appendTo($elem);
		this._refreshStream({
			docId: docId
		});

		// Insert 'Add Comment' section
		var $addSection = $('<div>')
			.addClass('n2Comment_add_section')
			.appendTo($elem);
		this._resetAddSection(doc, $addSection);
	},

	_refreshStream: function(opts_){
		var opts = $n2.extend({
			docId: null
		},opts_);

		var _this = this;

		var originId = opts.docId;
		var buttonDisplay = new $n2.couchDisplay.ButtonDisplay();

		// Get references
		this.documentSource.getReferencesFromOrigin({
			docId: originId
			,onSuccess: loadedDocIds
		});

		function loadedDocIds(refDocIds){
			// Get documents that include comments
			_this.documentSource.getDocuments({
				docIds: refDocIds
				,onSuccess: loadedDocs
			});
		};

		function loadedDocs(docs){
			// Cache documents to save show service from reloading them
			_this.cachedCommentsByDocId = {};
			docs.forEach(function(doc){
				var docId = doc._id;
				_this.cachedCommentsByDocId[docId] = doc;
			});

			// Sort comments by last updated time
			docs.sort(function(a,b){
				var aTime = undefined;
				if( a && a.nunaliit_created ){
					aTime = a.nunaliit_created.time;
				};
				if( a && a.nunaliit_last_updated ){
					aTime = a.nunaliit_last_updated.time;
				};

				var bTime = undefined;
				if( b && b.nunaliit_created ){
					bTime = b.nunaliit_created.time;
				};
				if( b && b.nunaliit_last_updated ){
					bTime = b.nunaliit_last_updated.time;
				};

				if( typeof aTime === 'number'
				 && typeof bTime === 'number' ){
					return bTime - aTime;
				};
				if( aTime ) return -1;
				if( bTime ) return 1;

				if( a._id > b._id ) {
					return 1;
				}
				return -1;
			});

			// Create comment stream tree
			var leavesByDocId = {};
			var tree = {};
			docs.forEach(function(doc){
				var docId = doc._id;
				var leaf = {
					docId: docId
					,doc: doc
					,replies: {}
					,inReplyToId: undefined
				};
				leavesByDocId[docId] = leaf;
			});
			for(var docId in leavesByDocId){
				var leaf = leavesByDocId[docId];
				var doc = leaf.doc;
				var topLevelLeaf = true;
				if( doc
				 && doc.nunaliit_source
				 && typeof doc.nunaliit_source.doc === 'string' ){
					var inReplyToId = doc.nunaliit_source.doc;
					var replyToLeaf = leavesByDocId[inReplyToId];
					if( replyToLeaf ){
						leaf.inReplyToId = inReplyToId;
						replyToLeaf.replies[docId] = leaf;
						topLevelLeaf = false;
					};
				};

				if( topLevelLeaf ){
					tree[docId] = leaf;
				};
			};

			// Retrieve display element cache
			var $reinsertElementsCache = $('#'+_this.reinsertElementsId);

			// Display comments
			var $stream = $('.n2Comment_stream_'+$n2.utils.stringToHtmlId(originId));
			if( $stream.length > 0 ){
				// Save elements that require surviving of redraw
				var $reinsertElements = $stream.find('.n2Comment_reinsert');
				$reinsertElements.appendTo($reinsertElementsCache);

				$stream.empty();
				docs.forEach(function(doc){
					var docId = doc._id;
					var $commentDiv = $('<div>')
						.addClass('n2Comment_doc n2Comment_doc_'+$n2.utils.stringToHtmlId(docId))
						.attr('n2DocId',docId)
						.appendTo($stream);
					var $content = $('<div>')
						.addClass('n2Comment_content')
						.appendTo($commentDiv);
					_this.showService.printDocument($content, docId);

					var $buttons = $('<div>')
						.addClass('n2Comment_buttons')
						.appendTo($commentDiv);

					buttonDisplay.drawButton({
						elem: $buttons
						,name: 'reply'
						,label: _loc('Reply')
						,click: function(){
							var $outer = $(this).parents('.n2Comment_doc');
							var docId = $outer.attr('n2DocId');
							_this._addReply(docId,$outer);
						}
					});

					buttonDisplay.drawButton({
						elem: $buttons
						,name: 'more_details'
						,label: _loc('More Details')
						,click: function(){
							var docId = $(this).parents('.n2Comment_doc').attr('n2DocId');
							_this._changeFocus(docId);
						}
					});

					// Re-insert the elements, if present
					$reinsertElementsCache
						.find('.n2Comment_reinsert_'+$n2.utils.stringToHtmlId(docId))
						.appendTo($commentDiv);
				});
			};

			// No need for cached elements, anymore
			$reinsertElementsCache.empty();
		};
	},

	_resetAddSection: function(doc, $addSection){
		var _this = this;

		var buttonDisplay = new $n2.couchDisplay.ButtonDisplay();

		$addSection.empty();
		buttonDisplay.drawButton({
			elem: $addSection
			,name: 'add_comment'
			,label: _loc('Add Comment')
			,click: function(){
				var $addSection = $(this).parents('.n2Comment_add_section');
				$addSection.empty();
				_this._addComment(doc,$addSection);
			}
		});

	},

	_addComment: function(doc, $elem){
		var _this = this;

		var createRelatedDocProcess = this.createDocProcess;
		var commentSchema = this.commentService.getCommentSchema();

		var $outerDiv = $('<div>')
			.addClass('n2Comment_reinsert n2Comment_reinsert_add')
			.appendTo($elem);

		createRelatedDocProcess.replyToDocument({
			doc: doc
			,schema: commentSchema
			,originDocId: doc._id
			,elem: $outerDiv
			,onSuccess: function(docId){
				_this._resetAddSection(doc, $elem);
			}
			,onError: function(err){
				_this._resetAddSection(doc, $elem);
			}
			,onCancel: function(){
				_this._resetAddSection(doc, $elem);
			}
		});
	},

	_addReply: function(docId, $outerDiv){
		var _this = this;
		var documentSource = this.documentSource;
		var createRelatedDocProcess = this.createDocProcess;
		var commentSchema = this.commentService.getCommentSchema();

		var $elem = undefined;
		if( $outerDiv.length > 0 ){
			$elem = $('<div>')
				.addClass('n2Comment_reply n2Comment_reinsert n2Comment_reinsert_'+$n2.utils.stringToHtmlId(docId))
				.appendTo($outerDiv);
		};

		documentSource.getDocument({
			docId: docId
			,onSuccess: function(doc){
				createRelatedDocProcess.replyToDocument({
					doc: doc
					,schema: commentSchema
					,elem: $elem
				});
			}
		});
	},

	_changeFocus: function(docId){
		var _this = this;

		this.dispatchService.send(DH,{
			type: 'userSelect'
			,docId: docId
		});
	},

	_handleDispatch: function(m, address, dispatcher){
		if( 'documentContentCreated' === m.type ){
			var doc = m.doc;
			this._handleDocumentContent(doc);

		} else if( 'documentContentUpdated' === m.type ){
			var doc = m.doc;
			this._handleDocumentContent(doc);

		} else if( 'documentDeleted' === m.type ){
			var docId = m.docId;
			this._handleDocumentDeleted(docId);

		} else if( 'cacheRetrieveDocument' === m.type ){
			// Synchronous call
			var docId = m.docId;
			if( this.cachedCommentsByDocId ){
				var doc = this.cachedCommentsByDocId[docId];

				if( doc ){
					m.doc = doc;
				};
			};
		};
	},

	_handleDocumentContent: function(doc){
		// Update cache
		if( this.cachedCommentsByDocId
		 && this.cachedCommentsByDocId[doc._id]){
			this.cachedCommentsByDocId[doc._id] = doc;
		};

		if( doc.nunaliit_origin
		 && typeof doc.nunaliit_origin.doc === 'string' ){
			var originId = doc.nunaliit_origin.doc;
			var $stream = $('.n2Comment_stream_'+$n2.utils.stringToHtmlId(originId));
			if( $stream.length > 0 ){
				// OK, need to refresh this stream
				this._refreshStream({
					docId: originId
				});
			};
		} else {
			// This document does not have an origin. Should no longer be part of a
			// comment stream
			var $commentDoc = $('.n2Comment_doc_'+$n2.utils.stringToHtmlId(doc._id));
			$commentDoc.remove();
		};
	},

	_handleDocumentDeleted: function(docId){
		// Remove associated streams
		var $section = $( '.n2Comment_stream_'+$n2.utils.stringToHtmlId(docId) );
		$section.remove();

		// Remove associated sections
		var $section = $( '.n2Comment_doc_'+$n2.utils.stringToHtmlId(docId) );
		$section.remove();
	}
});

//===================================================================================
var CommentService = $n2.Class({

	documentSource: null,

	showService: null,

	createDocProcess: null,

	dispatchService: null,

	customService: null,

	commentSchema: null,

	commentStreamDisplay: null,

	initialize: function(opts_){
		var opts = $n2.extend({
			documentSource: null
			,showService: null
			,createDocProcess: null
			,dispatchService: null
			,customService: null
			,commentSchema: null
		},opts_);

		this.documentSource = opts.documentSource;
		this.showService = opts.showService;
		this.createDocProcess = opts.createDocProcess;
		this.dispatchService = opts.dispatchService;
		this.customService = opts.customService;
		this.commentSchema = opts.commentSchema;
	},

	getCommentSchema: function(){
		return this.commentSchema;
	},

	setCommentSchema: function(commentSchema){
		this.commentSchema = commentSchema;

		if( this.commentSchema ){
			$('.n2Comment_button_addcomment')
				.removeClass('n2Comment_button_addcomment_unavailable')
				.addClass('n2Comment_button_addcomment_available');
		} else {
			$('.n2Comment_button_addcomment')
				.removeClass('n2Comment_button_addcomment_available')
				.addClass('n2Comment_button_addcomment_unavailable');
		};
	},

	/**
	 * Use this function to add a button to an element. The button
	 * initiates adding a comment associated with the given document.
	 */
	insertAddCommentButton: function(opts_){
		var opts = $n2.extend({
			div: null
			,doc: null
			,buttonDisplay: null
		},opts_);

		var _this = this;

		var doc = opts.doc;
		var $buttons = $(opts.div);

		var buttonDisplay = opts.buttonDisplay;
		if( !buttonDisplay ){
			buttonDisplay = new $n2.couchDisplay.ButtonDisplay();
		};

		var classNames = ['n2Comment_button_addcomment'];
		if( this.commentSchema ){
			classNames.push('n2Comment_button_addcomment_available');
		} else {
			classNames.push('n2Comment_button_addcomment_unavailable');
		};

		// Show 'add comment' button
		buttonDisplay.drawButton({
			elem: $buttons
			,name: 'add_comment'
			,label: _loc('Add Comment')
			,classNames: classNames
			,click: function(){
				_this._addComment(doc);
			}
		});
	},

	_addComment: function(doc){
		var createRelatedDocProcess = this.createDocProcess;
		createRelatedDocProcess.replyToDocument({
			doc: doc
			,schema: this.commentSchema
			,originDocId: doc._id
		});
	},

	getCommentStreamDisplay: function(){
		if( !this.commentStreamDisplay ){
			this.commentStreamDisplay = new CommentStreamDisplay({
				commentService: this
				,dispatchService: this.dispatchService
				,documentSource: this.documentSource
				,showService: this.showService
				,createDocProcess: this.createDocProcess
			});
		};

		return this.commentStreamDisplay;
	}
});

//===================================================================================

// Exports
$n2.comment = {
	Service: CommentService
	,CommentStreamDisplay: CommentStreamDisplay
};

})(jQuery,nunaliit2);
<|endoftext|>