<commit_before>const BroadlinkRMAccessory = require('./accessory');
const discoveredDevices = require('../helpers/devices.js');
const sendData = require('../helpers/sendData');

class AirConAccessory extends BroadlinkRMAccessory {

  constructor (log, config) {
    super(log, config)

    const { defaultCoolTemperature, defaultHeatTemperature, heatTemperature, minTemperature, maxTemperature, pseudoDeviceTemperature, replaceAutoMode, units } = config

    this.log = log;
    this.currentHeatingCoolingState = Characteristic.CurrentHeatingCoolingState.OFF
    this.targetTemperature = minTemperature
    this.firstTemperatureUpdate = true

    config.minTemperature = minTemperature || 0
    config.maxTemperature = maxTemperature || 30

    config.temperatureDisplayUnits = (units && units.toLowerCase() === 'f') ? Characteristic.TemperatureDisplayUnits.FAHRENHEIT : Characteristic.TemperatureDisplayUnits.CELSIUS;

    // When a temperature hex doesn't exist we try to use the hex set for these
    // default temperatures
    config.defaultCoolTemperature = defaultCoolTemperature || 16
    config.defaultHeatTemperature = defaultHeatTemperature || 30

    // Used to determine when we should use the defaultHeatTemperature or the
    // defaultHeatTemperature
    config.heatTemperature = heatTemperature || 22

    // When we turn on the thermostat with Siri it comes thrugh as "auto" which
    // isn't particularly supported at this time so we convert the mode to cool
    // or heat
    // Note that this is only used when you use Siri or press Auto immediately
    // after launching Homebridge. The rest of the time we'll use your last known
    // temperature
    config.replaceAutoMode = replaceAutoMode || 'cool'

    if (config.pseudoDeviceTemperature > config.minTemperature) throw new Error(`The pseudoDeviceTemperature (${pseudoDeviceTemperature}) must be less than the minTemperature (${minTemperature})`)

    this.callbackQueue = {}
  }

  getServices () {
    const services = super.getServices();
    const { data, config, name } = this;
    const { minTemperature, maxTemperature } = config

    const service = new Service.Thermostat(name);

    // Required Characteristics
    service
      .getCharacteristic(Characteristic.CurrentHeatingCoolingState)
      .on('get', this.getCurrentHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.TargetHeatingCoolingState)
      .on('get', this.getTargetHeatingCoolingState.bind(this))
      .on('set', this.setTargetHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .on('get', this.getCurrentTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .on('get', this.getTargetTemperature.bind(this))
      .on('set', this.setTargetTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TemperatureDisplayUnits)
      .on('get', this.getTemperatureDisplayUnits.bind(this))
      .on('set', this.setTemperatureDisplayUnits.bind(this));

    //
    // service
    //   .getCharacteristic(Characteristic.On)
    //   .on('get', this.getOnOffState.bind(this))
    //   .on('set', this.setOnOffState.bind(this));

    service
      .getCharacteristic(Characteristic.Name)
      .on('get', this.getName.bind(this, name));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    this.thermostatService = service
    services.push(service)

    return services
  }

  updateServiceHeatingCoolingState (value) {
    if (this.currentHeatingCoolingState === value) return

    this.currentHeatingCoolingState = value

    // this.log(`setCharacteristic for CurrentHeatingCoolingState and TargetHeatingCoolingState (${value})`)
    this.thermostatService.setCharacteristic(Characteristic.CurrentHeatingCoolingState, value);
    this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, value);
  }

  // Thermostat
  sendTemperature (temperature, callback) {
    const { config, data, host, log } = this
    const { defaultHeatTemperature, defaultCoolTemperature, heatTemperature } = config

    log(`Potentially sendTemperature (${temperature})`);

    let hasTemperatureChanged = (this.targetTemperature !== temperature)
    let hexData = data[`temperature${temperature}`]

    // You may not want to set the hex data for every single mode...
    if (!hexData) {
      const defaultTemperature = (temperature >= heatTemperature) ? defaultHeatTemperature : defaultCoolTemperature
      hexData = data[`temperature${defaultTemperature}`]

      if (!hexData) {
        const error = Error(`You need to provide a hex code for the given mode/temperature
          ({ "temperature${this.targetTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })
          or at the very least, the default mode/temperature
          ({ "temperature${defaultTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })`)

        log(error.message)

        return callback(error)
      }

      hasTemperatureChanged = (this.targetTemperature !== defaultTemperature)
      this.log(`Update to default temperature (${defaultTemperature})`);

      this.targetTemperature = defaultTemperature
    } else {
      this.targetTemperature = temperature
    }

    if (!hasTemperatureChanged && !this.firstTemperatureUpdate && this.currentHeatingCoolingState !== Characteristic.TargetHeatingCoolingState.OFF) return callback()

    this.firstTemperatureUpdate = false

    const mode = hexData['pseudo-mode']
    this.log(`sendTemperature (${this.targetTemperature}, ${mode})`);
    this.updateServiceHeatingCoolingState(this.heatingCoolingStateForConfigKey(hexData['pseudo-mode']))

    this.thermostatService.setCharacteristic(Characteristic.TargetTemperature, this.targetTemperature);

    this.lastUsedTemperature = this.targetTemperature
    this.lastUsedHeatingCoolingState= this.currentHeatingCoolingState

    sendData(host, hexData.data, log)
    callback()
  }

  setOnOffState (newState, callback) {
    const { log } = this
    log(`setOnOffState: ${newState}`);

    setTimeout(() => {
      this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey('auto'));
    }, 100)

    callback()
  }

  getOnOffState (callback) {
    this.log(`getSwitchState: ${this.switchState}`);

    callback(null, this.switchState)
  }

	getCurrentHeatingCoolingState (callback) {
		this.log('getCurrentHeatingCoolingState');

    this.updateServiceHeatingCoolingState(this.currentHeatingCoolingState)

    let off = (!this.lastUsedHeatingCoolingState || (this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('off')))

    callback(null, !off);
	}

	getTargetHeatingCoolingState (callback) {
    const currentModeConfigKey = this.configKeyForCurrentHeatingCoolingState()
		this.log(`getTargetHeatingCoolingState (${currentModeConfigKey})`);

		callback(null, this.currentHeatingCoolingState); // success
	}

	setTargetHeatingCoolingState (value, callback) {
    const { config, data, host, log } = this
    const { defaultCoolTemperature, defaultHeatTemperature, replaceAutoMode } = config

    // Perform the auto -> cool/heat conversion descrived in constructor()
    if (replaceAutoMode && this.configKeyForHeatingCoolingState(value) === 'auto') {
      if (this.firstTemperatureUpdate || this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('auto')) {
     		log(`setTargetHeatingCoolingState (converting from auto to ${replaceAutoMode})`);

        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey(replaceAutoMode));
        }, 300)
      } else {
        this.targetTemperature = this.lastUsedTemperature

        log(`setTargetHeatingCoolingState (converting from auto to last used - ${this.configKeyForHeatingCoolingState(this.lastUsedHeatingCoolingState)})`);
        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.lastUsedHeatingCoolingState);
        }, 300)
      }

      return callback()
    }

    if (value === undefined) return callback(); // Some calls are made to this without a value
    if (value === this.currentHeatingCoolingState) return callback()

    let temperature = this.targetTemperature

    if (value === Characteristic.TargetHeatingCoolingState.HEAT) {
      temperature = defaultHeatTemperature
    } else if (value === Characteristic.TargetHeatingCoolingState.COOL) {
      temperature = defaultCoolTemperature
    }

    const currentModeConfigKey = this.configKeyForHeatingCoolingState(value)
    log(`setTargetHeatingCoolingState (${currentModeConfigKey})`);

    if (currentModeConfigKey === 'off') {
      this.updateServiceHeatingCoolingState(value)
      sendData(host, data.off, log)
      callback()
    } else {
      this.sendTemperature(temperature, callback)
    }
	}

	getCurrentTemperature (callback) {
    const { config, host, log } = this;
    const { minTemperature, pseudoDeviceTemperature } = config

    // Some devices don't include a thermometer
    if (pseudoDeviceTemperature !== undefined) {
      log('getCurrentTemperature (ignored)')
      return callback(null, pseudoDeviceTemperature)
    }

    let device;

    if (host) {
      device = discoveredDevices[host];
    } else {
      const hosts = Object.keys(discoveredDevices)
      if (hosts.length === 0) {
        log(`getCurrentTemperature (no devices found)`);

        return callback(null, 0)
      }

      device = discoveredDevices[hosts[0]];
    }

    if (!device) {
      log(`getCurrentTemperature (no device found at ${host})`);

      return callback(null, 0)
    }

    const callbackIdentifier = Date.now()
    this.callbackQueue[callbackIdentifier] = callback

    // Make sure we're only calling one at a time
    if (Object.keys(this.callbackQueue).length > 1) return

    let onTemperature

    onTemperature = (temperature) => {
      this.currentTemperature = temperature;

      if (this.removeTemperatureListenerTimer) clearTimeout(this.removeTemperatureListenerTimer)
      device.removeListener('temperature', onTemperature);
      this.processQueuedCallbacks()

      log(`getCurrentTemperature (${temperature})`);
 		}

    // Add a 3 second timeout
    this.removeTemperatureListenerTimer = setTimeout(() => {
      device.removeListener('temperature');
      this.processQueuedCallbacks()

   		log(`getCurrentTemperature (3s timeout)`);
    }, 3000)

   	device.on('temperature', onTemperature);

 		device.checkTemperature();
	}

  processQueuedCallbacks () {
    Object.keys(this.callbackQueue).forEach((callbackIdentifier) => {
      const callback = this.callbackQueue[callbackIdentifier]
      callback(null, this.currentTemperature);

      delete this.callbackQueue[callbackIdentifier]
    })
  }

	getTargetTemperature (callback) {
		this.log(`getTargetTemperature (${this.targetTemperature})`);
		callback(null, this.targetTemperature)
	}

	setTargetTemperature (value, callback) {
		this.log(`setTargetTemperature (${value})`);

    this.sendTemperature(value, callback)
	}

	getTemperatureDisplayUnits (callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`getTemperatureDisplayUnits (${temperatureDisplayUnits})`);

		callback(null, temperatureDisplayUnits);
	}

	setTemperatureDisplayUnits (value, callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`setTemperatureDisplayUnits from ${temperatureDisplayUnits} to ${value}`);

		config.temperatureDisplayUnits = value;

		callback(null);
	}

  getName (name, callback) {
		this.log(`getName: ${name}`);

		callback(null, name);
	}

  configKeyForCurrentHeatingCoolingState () {
    return this.configKeyForHeatingCoolingState(this.currentHeatingCoolingState)
  }

  configKeyForHeatingCoolingState (state) {
    switch (state) {
      case Characteristic.TargetHeatingCoolingState.AUTO:
        return 'auto'
      case Characteristic.TargetHeatingCoolingState.COOL:
        return 'cool'
      case Characteristic.TargetHeatingCoolingState.HEAT:
        return 'heat'
      default:
        return 'off'
    }
  }

  heatingCoolingStateForConfigKey (configKey) {
    switch (configKey) {
      case 'off':
        return Characteristic.TargetHeatingCoolingState.OFF
      case 'cool':
        return Characteristic.TargetHeatingCoolingState.COOL
      case 'heat':
        return Characteristic.TargetHeatingCoolingState.HEAT
      default:
        return Characteristic.TargetHeatingCoolingState.AUTO
    }
  }
}

module.exports = AirConAccessory
<commit_msg>No need to show “pseudoDeviceTemperature must be less than the minTemperature“ error<commit_after>const BroadlinkRMAccessory = require('./accessory');
const discoveredDevices = require('../helpers/devices.js');
const sendData = require('../helpers/sendData');

class AirConAccessory extends BroadlinkRMAccessory {

  constructor (log, config) {
    super(log, config)

    const { defaultCoolTemperature, defaultHeatTemperature, heatTemperature, minTemperature, maxTemperature, pseudoDeviceTemperature, replaceAutoMode, units } = config

    this.log = log;
    this.currentHeatingCoolingState = Characteristic.CurrentHeatingCoolingState.OFF
    this.targetTemperature = minTemperature
    this.firstTemperatureUpdate = true

    config.minTemperature = minTemperature || 0
    config.maxTemperature = maxTemperature || 30

    config.temperatureDisplayUnits = (units && units.toLowerCase() === 'f') ? Characteristic.TemperatureDisplayUnits.FAHRENHEIT : Characteristic.TemperatureDisplayUnits.CELSIUS;

    // When a temperature hex doesn't exist we try to use the hex set for these
    // default temperatures
    config.defaultCoolTemperature = defaultCoolTemperature || 16
    config.defaultHeatTemperature = defaultHeatTemperature || 30

    // Used to determine when we should use the defaultHeatTemperature or the
    // defaultHeatTemperature
    config.heatTemperature = heatTemperature || 22

    // When we turn on the thermostat with Siri it comes thrugh as "auto" which
    // isn't particularly supported at this time so we convert the mode to cool
    // or heat
    // Note that this is only used when you use Siri or press Auto immediately
    // after launching Homebridge. The rest of the time we'll use your last known
    // temperature
    config.replaceAutoMode = replaceAutoMode || 'cool'

    this.callbackQueue = {}
  }

  getServices () {
    const services = super.getServices();
    const { data, config, name } = this;
    const { minTemperature, maxTemperature } = config

    const service = new Service.Thermostat(name);

    // Required Characteristics
    service
      .getCharacteristic(Characteristic.CurrentHeatingCoolingState)
      .on('get', this.getCurrentHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.TargetHeatingCoolingState)
      .on('get', this.getTargetHeatingCoolingState.bind(this))
      .on('set', this.setTargetHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .on('get', this.getCurrentTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .on('get', this.getTargetTemperature.bind(this))
      .on('set', this.setTargetTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TemperatureDisplayUnits)
      .on('get', this.getTemperatureDisplayUnits.bind(this))
      .on('set', this.setTemperatureDisplayUnits.bind(this));

    //
    // service
    //   .getCharacteristic(Characteristic.On)
    //   .on('get', this.getOnOffState.bind(this))
    //   .on('set', this.setOnOffState.bind(this));

    service
      .getCharacteristic(Characteristic.Name)
      .on('get', this.getName.bind(this, name));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    this.thermostatService = service
    services.push(service)

    return services
  }

  updateServiceHeatingCoolingState (value) {
    if (this.currentHeatingCoolingState === value) return

    this.currentHeatingCoolingState = value

    // this.log(`setCharacteristic for CurrentHeatingCoolingState and TargetHeatingCoolingState (${value})`)
    this.thermostatService.setCharacteristic(Characteristic.CurrentHeatingCoolingState, value);
    this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, value);
  }

  // Thermostat
  sendTemperature (temperature, callback) {
    const { config, data, host, log } = this
    const { defaultHeatTemperature, defaultCoolTemperature, heatTemperature } = config

    log(`Potentially sendTemperature (${temperature})`);

    let hasTemperatureChanged = (this.targetTemperature !== temperature)
    let hexData = data[`temperature${temperature}`]

    // You may not want to set the hex data for every single mode...
    if (!hexData) {
      const defaultTemperature = (temperature >= heatTemperature) ? defaultHeatTemperature : defaultCoolTemperature
      hexData = data[`temperature${defaultTemperature}`]

      if (!hexData) {
        const error = Error(`You need to provide a hex code for the given mode/temperature
          ({ "temperature${this.targetTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })
          or at the very least, the default mode/temperature
          ({ "temperature${defaultTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })`)

        log(error.message)

        return callback(error)
      }

      hasTemperatureChanged = (this.targetTemperature !== defaultTemperature)
      this.log(`Update to default temperature (${defaultTemperature})`);

      this.targetTemperature = defaultTemperature
    } else {
      this.targetTemperature = temperature
    }

    if (!hasTemperatureChanged && !this.firstTemperatureUpdate && this.currentHeatingCoolingState !== Characteristic.TargetHeatingCoolingState.OFF) return callback()

    this.firstTemperatureUpdate = false

    const mode = hexData['pseudo-mode']
    this.log(`sendTemperature (${this.targetTemperature}, ${mode})`);
    this.updateServiceHeatingCoolingState(this.heatingCoolingStateForConfigKey(hexData['pseudo-mode']))

    this.thermostatService.setCharacteristic(Characteristic.TargetTemperature, this.targetTemperature);

    this.lastUsedTemperature = this.targetTemperature
    this.lastUsedHeatingCoolingState= this.currentHeatingCoolingState

    sendData(host, hexData.data, log)
    callback()
  }

  setOnOffState (newState, callback) {
    const { log } = this
    log(`setOnOffState: ${newState}`);

    setTimeout(() => {
      this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey('auto'));
    }, 100)

    callback()
  }

  getOnOffState (callback) {
    this.log(`getSwitchState: ${this.switchState}`);

    callback(null, this.switchState)
  }

	getCurrentHeatingCoolingState (callback) {
		this.log('getCurrentHeatingCoolingState');

    this.updateServiceHeatingCoolingState(this.currentHeatingCoolingState)

    let off = (!this.lastUsedHeatingCoolingState || (this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('off')))

    callback(null, !off);
	}

	getTargetHeatingCoolingState (callback) {
    const currentModeConfigKey = this.configKeyForCurrentHeatingCoolingState()
		this.log(`getTargetHeatingCoolingState (${currentModeConfigKey})`);

		callback(null, this.currentHeatingCoolingState); // success
	}

	setTargetHeatingCoolingState (value, callback) {
    const { config, data, host, log } = this
    const { defaultCoolTemperature, defaultHeatTemperature, replaceAutoMode } = config

    // Perform the auto -> cool/heat conversion descrived in constructor()
    if (replaceAutoMode && this.configKeyForHeatingCoolingState(value) === 'auto') {
      if (this.firstTemperatureUpdate || this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('auto')) {
     		log(`setTargetHeatingCoolingState (converting from auto to ${replaceAutoMode})`);

        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey(replaceAutoMode));
        }, 300)
      } else {
        this.targetTemperature = this.lastUsedTemperature

        log(`setTargetHeatingCoolingState (converting from auto to last used - ${this.configKeyForHeatingCoolingState(this.lastUsedHeatingCoolingState)})`);
        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.lastUsedHeatingCoolingState);
        }, 300)
      }

      return callback()
    }

    if (value === undefined) return callback(); // Some calls are made to this without a value
    if (value === this.currentHeatingCoolingState) return callback()

    let temperature = this.targetTemperature

    if (value === Characteristic.TargetHeatingCoolingState.HEAT) {
      temperature = defaultHeatTemperature
    } else if (value === Characteristic.TargetHeatingCoolingState.COOL) {
      temperature = defaultCoolTemperature
    }

    const currentModeConfigKey = this.configKeyForHeatingCoolingState(value)
    log(`setTargetHeatingCoolingState (${currentModeConfigKey})`);

    if (currentModeConfigKey === 'off') {
      this.updateServiceHeatingCoolingState(value)
      sendData(host, data.off, log)
      callback()
    } else {
      this.sendTemperature(temperature, callback)
    }
	}

	getCurrentTemperature (callback) {
    const { config, host, log } = this;
    const { minTemperature, pseudoDeviceTemperature } = config

    // Some devices don't include a thermometer
    if (pseudoDeviceTemperature !== undefined) {
      log('getCurrentTemperature (ignored)')
      return callback(null, pseudoDeviceTemperature)
    }

    let device;

    if (host) {
      device = discoveredDevices[host];
    } else {
      const hosts = Object.keys(discoveredDevices)
      if (hosts.length === 0) {
        log(`getCurrentTemperature (no devices found)`);

        return callback(null, 0)
      }

      device = discoveredDevices[hosts[0]];
    }

    if (!device) {
      log(`getCurrentTemperature (no device found at ${host})`);

      return callback(null, 0)
    }

    const callbackIdentifier = Date.now()
    this.callbackQueue[callbackIdentifier] = callback

    // Make sure we're only calling one at a time
    if (Object.keys(this.callbackQueue).length > 1) return

    let onTemperature

    onTemperature = (temperature) => {
      this.currentTemperature = temperature;

      if (this.removeTemperatureListenerTimer) clearTimeout(this.removeTemperatureListenerTimer)
      device.removeListener('temperature', onTemperature);
      this.processQueuedCallbacks()

      log(`getCurrentTemperature (${temperature})`);
 		}

    // Add a 3 second timeout
    this.removeTemperatureListenerTimer = setTimeout(() => {
      device.removeListener('temperature');
      this.processQueuedCallbacks()

   		log(`getCurrentTemperature (3s timeout)`);
    }, 3000)

   	device.on('temperature', onTemperature);

 		device.checkTemperature();
	}

  processQueuedCallbacks () {
    Object.keys(this.callbackQueue).forEach((callbackIdentifier) => {
      const callback = this.callbackQueue[callbackIdentifier]
      callback(null, this.currentTemperature);

      delete this.callbackQueue[callbackIdentifier]
    })
  }

	getTargetTemperature (callback) {
		this.log(`getTargetTemperature (${this.targetTemperature})`);
		callback(null, this.targetTemperature)
	}

	setTargetTemperature (value, callback) {
		this.log(`setTargetTemperature (${value})`);

    this.sendTemperature(value, callback)
	}

	getTemperatureDisplayUnits (callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`getTemperatureDisplayUnits (${temperatureDisplayUnits})`);

		callback(null, temperatureDisplayUnits);
	}

	setTemperatureDisplayUnits (value, callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`setTemperatureDisplayUnits from ${temperatureDisplayUnits} to ${value}`);

		config.temperatureDisplayUnits = value;

		callback(null);
	}

  getName (name, callback) {
		this.log(`getName: ${name}`);

		callback(null, name);
	}

  configKeyForCurrentHeatingCoolingState () {
    return this.configKeyForHeatingCoolingState(this.currentHeatingCoolingState)
  }

  configKeyForHeatingCoolingState (state) {
    switch (state) {
      case Characteristic.TargetHeatingCoolingState.AUTO:
        return 'auto'
      case Characteristic.TargetHeatingCoolingState.COOL:
        return 'cool'
      case Characteristic.TargetHeatingCoolingState.HEAT:
        return 'heat'
      default:
        return 'off'
    }
  }

  heatingCoolingStateForConfigKey (configKey) {
    switch (configKey) {
      case 'off':
        return Characteristic.TargetHeatingCoolingState.OFF
      case 'cool':
        return Characteristic.TargetHeatingCoolingState.COOL
      case 'heat':
        return Characteristic.TargetHeatingCoolingState.HEAT
      default:
        return Characteristic.TargetHeatingCoolingState.AUTO
    }
  }
}

module.exports = AirConAccessory
<|endoftext|>


<commit_before>var consoleTest = /(^.|\b)console\./;

var iframedelay = (function () {
  var iframedelay = { active : false },
      iframe = document.createElement('iframe'),
      doc,
      callbackName = '__callback' + (+new Date);

  iframe.style.height = iframe.style.width = '1px';
  iframe.style.visibility = 'hidden';
  document.body.appendChild(iframe);
  doc = iframe.contentDocument || iframe.contentWindow.document;

  window[callbackName] = function (width) {
    iframedelay.active = width === 0;
    try {
      iframe.parentNode.removeChild(iframe);
      delete window[callbackName];
    } catch (e){}
  };

  try {
    doc.open();
    doc.write('');
    doc.close();
  } catch (e) {
    iframedelay.active = true;
  }

  return iframedelay;
}());

var useCustomConsole = !(function () {
  var ok = typeof window.console !== 'undefined';
  try {
    window.console.log('jsbin init test');
  } catch (e) {
    ok = false;
  }
  return ok;
})();

var re = null;

function getPreparedCode() {
  // init the regular expression cache because this function
  // is called much earlier than the above code is actually encountered
  // yay for massive .js app!
  if (!re) {
    re = {
      docReady: /\$\(document\)\.ready/,
      shortDocReady: /\$\(function/,
      console: /(^.|\b)console\./g,
      script: /<\/script/ig,
      code: /%code%/,
      title: /<title>(.*)<\/title>/i,
      winLoad: /window\.onload\s*=/
    };
  }

  var parts = [],
      source = '',
      js = '',
      css = '',
      close = '',
      hasHTML = false,
      hasCSS = false,
      hasJS = false;

  try {
    source = editors.html.render();
  } catch (e) {
    console.error(e.message);
  }

  hasHTML = !!$.trim(source);

  try {
    js = editors.javascript.render();
    js += '\n//@ sourceURL: ' + jsbin.getURL().split('/').pop() + '.js';
  } catch (e) {
    console.error(e.message);
  }

  hasJS = !!$.trim(js);

  try {
    css = editors.css.render();
  } catch (e) {
    console.error(e.message);
  }

  hasCSS = !!$.trim(css);

  // escape any script tags in the JS code, because that'll break the mushing together
  js = js.replace(re.script, '<\\/script');

  // note that I'm using split and reconcat instead of replace, because if the js var
  // contains '$$' it's replaced to '$' - thus breaking Prototype code. This method
  // gets around the problem.
  if (!hasHTML && hasJS) {
    source = "
\n" + js + "
";
  } else if (re.code.test(source)) {
    parts = source.split('%code%');
    source = parts[0] + js + parts[1];
  } else if (hasJS) {
    close = '';
    if (source.indexOf('') !== -1) {
      parts.push(source.substring(0, source.lastIndexOf('')));
      parts.push(source.substring(source.lastIndexOf('')));

      source = parts[0];
      close = parts.length == 2 && parts[1] ? parts[1] : '';
    }
    if (useCustomConsole) {
      source += "\n\n" + close;
    // RS: not sure why I ran this in closure, but it means the expected globals are no longer so
    // source += "\n" + close;
    var type = jsbin.settings && jsbin.settings.processors && jsbin.settings.processors.javascript ? ' type="text/' + jsbin.settings.processors.javascript + '"' : '';
    source += "\n" + close;
  }

  // redirect console logged to our custom log while debugging
  if (re.console.test(source)) {
    if (jsbin.panels.panels.console.visible) {
      source = source.replace(re.console, 'window.top._console.');
    } else {
      source = source.replace(re.console, 'window.top.console.');
    }
  }

  if (!hasHTML && !hasJS && hasCSS) {
    source = "
\n" + css + "
";
  } else if (css) {
    parts = [];
    close = '';
    if (source.indexOf('') !== -1) {
      parts.push(source.substring(0, source.lastIndexOf('')))
      parts.push(source.substring(source.lastIndexOf('')));

      source = parts[0];
      close = parts.length == 2 && parts[1] ? parts[1] : '';
    }
    source += '\n' + close;
  }

  // specific change for rendering $(document).ready() because iframes doesn't trigger ready (TODO - really test in IE, may have been fixed...)
  if (re.docReady.test(source)) {
    source = source.replace(re.docReady, 'window.onload = ');
  }

  if (re.shortDocReady.test(source)) {
    source = source.replace(re.shortDocReady, 'window.onload = (function');
  }

  // read the element out of the source code and plug it in to our document.title
  var newDocTitle = source.match(re.title);
  if (newDocTitle !== null && newDocTitle[1] !== documentTitle) {
    documentTitle = newDocTitle[1];
    updateTitle(!/ \[unsaved\]/.test(document.title));
  }

  return source;
}

// function renderPreview() {
//   var doc = $('#preview iframe')[0],
//       win = doc.contentDocument || doc.contentWindow.document,
//       source = getPreparedCode();

//   var run = function () {
//     win.open();
//     if (jsbin.settings.debug) {
//       win.write('
' + source.replace(/[<>&]/g, function (m) {
//         if (m == '<') return '<';
//         if (m == '>') return '>';
//         if (m == '"') return '"';
//       }) + '
');
//     } else {
//       win.write(source);
//     }
//     win.close();
//   };

//   // WebKit requires a wait time before actually writing to the iframe
//   // annoyingly it's not consistent (I suspect WebKit is the buggy one)
//   if (iframedelay.active) {
//     // this setTimeout allows the iframe to be rendered before our code
//     // runs - thus allowing us access to the innerWidth, et al
//     setTimeout(run, 10);
//   } else {
//     run();
//   }
// }
<commit_msg>Fixed regexp by reseting the last index. ::Sigh::<commit_after>var consoleTest = /(^.|\b)console\./;

var iframedelay = (function () {
  var iframedelay = { active : false },
      iframe = document.createElement('iframe'),
      doc,
      callbackName = '__callback' + (+new Date);

  iframe.style.height = iframe.style.width = '1px';
  iframe.style.visibility = 'hidden';
  document.body.appendChild(iframe);
  doc = iframe.contentDocument || iframe.contentWindow.document;

  window[callbackName] = function (width) {
    iframedelay.active = width === 0;
    try {
      iframe.parentNode.removeChild(iframe);
      delete window[callbackName];
    } catch (e){}
  };

  try {
    doc.open();
    doc.write('');
    doc.close();
  } catch (e) {
    iframedelay.active = true;
  }

  return iframedelay;
}());

var useCustomConsole = !(function () {
  var ok = typeof window.console !== 'undefined';
  try {
    window.console.log('jsbin init test');
  } catch (e) {
    ok = false;
  }
  return ok;
})();

var re = null;

function getPreparedCode() {
  // init the regular expression cache because this function
  // is called much earlier than the above code is actually encountered
  // yay for massive .js app!
  if (!re) {
    re = {
      docReady: /\$\(document\)\.ready/,
      shortDocReady: /\$\(function/,
      console: /(^.|\b)console\./g,
      script: /<\/script/ig,
      code: /%code%/,
      title: /<title>(.*)<\/title>/i,
      winLoad: /window\.onload\s*=/
    };
  }

  // reset all the regexp positions for reuse
  re.docReady.lastIndex = 0;
  re.shortDocReady.lastIndex = 0;
  re.console.lastIndex = 0;
  re.script.lastIndex = 0;
  re.code.lastIndex = 0;
  re.title.lastIndex = 0;
  re.winLoad.lastIndex = 0;

  var parts = [],
      source = '',
      js = '',
      css = '',
      close = '',
      hasHTML = false,
      hasCSS = false,
      hasJS = false;

  try {
    source = editors.html.render();
  } catch (e) {
    console.error(e.message);
  }

  hasHTML = !!$.trim(source);

  try {
    js = editors.javascript.render();
    js += '\n//@ sourceURL: ' + jsbin.getURL().split('/').pop() + '.js';
  } catch (e) {
    console.error(e.message);
  }

  hasJS = !!$.trim(js);

  try {
    css = editors.css.render();
  } catch (e) {
    console.error(e.message);
  }

  hasCSS = !!$.trim(css);

  // escape any script tags in the JS code, because that'll break the mushing together
  js = js.replace(re.script, '<\\/script');

  // note that I'm using split and reconcat instead of replace, because if the js var
  // contains '$$' it's replaced to '$' - thus breaking Prototype code. This method
  // gets around the problem.
  if (!hasHTML && hasJS) {
    source = "
\n" + js + "
";
  } else if (re.code.test(source)) {
    parts = source.split('%code%');
    source = parts[0] + js + parts[1];
  } else if (hasJS) {
    close = '';
    if (source.indexOf('') !== -1) {
      parts.push(source.substring(0, source.lastIndexOf('')));
      parts.push(source.substring(source.lastIndexOf('')));

      source = parts[0];
      close = parts.length == 2 && parts[1] ? parts[1] : '';
    }

    // FIXME is this even worth while now we have our own console? RS July 1, 2012
    if (useCustomConsole) {
      source += "\n\n" + close;
    var type = jsbin.settings && jsbin.settings.processors && jsbin.settings.processors.javascript ? ' type="text/' + jsbin.settings.processors.javascript + '"' : '';
    source += "\n" + close;
  }

  // redirect console logged to our custom log while debugging
  if (re.console.test(source)) {
    if (jsbin.panels.panels.console.visible) {
      source = source.replace(re.console, 'window.top._console.');
    } else {
      source = source.replace(re.console, 'window.top.console.');
    }
  }

  if (!hasHTML && !hasJS && hasCSS) {
    source = "
\n" + css + "
";
  } else if (css) {
    parts = [];
    close = '';
    if (source.indexOf('') !== -1) {
      parts.push(source.substring(0, source.lastIndexOf('')))
      parts.push(source.substring(source.lastIndexOf('')));

      source = parts[0];
      close = parts.length == 2 && parts[1] ? parts[1] : '';
    }
    source += '\n' + close;
  }

  // specific change for rendering $(document).ready() because iframes doesn't trigger ready (TODO - really test in IE, may have been fixed...)
  if (re.docReady.test(source)) {
    source = source.replace(re.docReady, 'window.onload = ');
  }

  if (re.shortDocReady.test(source)) {
    source = source.replace(re.shortDocReady, 'window.onload = (function');
  }

  // read the element out of the source code and plug it in to our document.title
  var newDocTitle = source.match(re.title);
  if (newDocTitle !== null && newDocTitle[1] !== documentTitle) {
    documentTitle = newDocTitle[1];
    updateTitle(!/ \[unsaved\]/.test(document.title));
  }

  return source;
}

// function renderPreview() {
//   var doc = $('#preview iframe')[0],
//       win = doc.contentDocument || doc.contentWindow.document,
//       source = getPreparedCode();

//   var run = function () {
//     win.open();
//     if (jsbin.settings.debug) {
//       win.write('
' + source.replace(/[<>&]/g, function (m) {
//         if (m == '<') return '<';
//         if (m == '>') return '>';
//         if (m == '"') return '"';
//       }) + '
');
//     } else {
//       win.write(source);
//     }
//     win.close();
//   };

//   // WebKit requires a wait time before actually writing to the iframe
//   // annoyingly it's not consistent (I suspect WebKit is the buggy one)
//   if (iframedelay.active) {
//     // this setTimeout allows the iframe to be rendered before our code
//     // runs - thus allowing us access to the innerWidth, et al
//     setTimeout(run, 10);
//   } else {
//     run();
//   }
// }
<|endoftext|>


<commit_before>// Hello World in JavaScript
importClass(Packages.com.sun.star.uno.UnoRuntime);
importClass(Packages.com.sun.star.text.XTextDocument);
importClass(Packages.com.sun.star.text.XText);
importClass(Packages.com.sun.star.text.XTextRange);

oDoc = XSCRIPTCONTEXT.getDocument();
xTextDoc = UnoRuntime.queryInterface(XTextDocument,oDoc);
xText = xTextDoc.getText();
xTextRange = xText.getEnd();
xTextRange.setString( "Hello World (in JavaScript)" );
<commit_msg>INTEGRATION: CWS scriptingf6 (1.2.22); FILE MERGED 2004/09/16 15:20:24 dfoster 1.2.22.1: #i33050# Added documentation to template javascript script<commit_after>// Hello World in JavaScript
// Import standard OpenOffice.org API classes. For more information on
// these classes and the OpenOffice.org API, see the OpenOffice.org
// Developers Guide at:
// http://api.openoffice.org/

importClass(Packages.com.sun.star.uno.UnoRuntime);
importClass(Packages.com.sun.star.text.XTextDocument);
importClass(Packages.com.sun.star.text.XText);
importClass(Packages.com.sun.star.text.XTextRange);

// Import XScriptContext class. An instance of this class is available
// to all JavaScript scripts in the global variable "XSCRIPTCONTEXT". This
// variable can be used to access the document for which this script
// was invoked.
//
// Methods available are:
//
//   XSCRIPTCONTEXT.getDocument() returns XModel
//   XSCRIPTCONTEXT.getDesktop() returns XDesktop
//   XSCRIPTCONTEXT.getComponentContext() returns XComponentContext
//
// For more information on using this class see the scripting
// developer guides at:
//
//   http://framework.openoffice.org/scripting/index.html
oDoc = XSCRIPTCONTEXT.getDocument();
xTextDoc = UnoRuntime.queryInterface(XTextDocument,oDoc);
xText = xTextDoc.getText();
xTextRange = xText.getEnd();
xTextRange.setString( "Hello World (in JavaScript)" );
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { Table, Column, Cell } from 'fixed-data-table-2';
import _ from 'underscore';

// import '../dist/fixed-data-table.css';
// import '../dist/agr.css';

import { ASC, DESC, NONE } from './constants';
import Header from './components/Header';
import Download from './components/Download';
import Filter from './components/ColumnFilter';
import Dimensions from 'react-dimensions';
import debug from 'debug';

class FlyBaseDataGrid extends Component {
  constructor(props) {
    super(props);
    this.state = {
      sortDir: this.initSortCols(),
      items: props.data,
      filter: '',
    };
    this.handleSort   = this.handleSort.bind(this);
    this.handleColumnFilter = this.handleColumnFilter.bind(this);
  }

  initSortCols() {
    const sortCols = {};
    this.props.columns.forEach((col) => sortCols[col.id] = NONE);
    return sortCols;
  }

  getTextFormatter(columnId) {
    const columnDef = _.find(this.props.columns, c => c.id === columnId);
    return (columnDef && columnDef.getText) || (v => v.toString());
  }

  handleColumnFilter(e, column){
    const getText = this.getTextFormatter(column);
    //  reset all column filter boxes to "" on focus change
    //  reset grid to default items
    const filterText = e.target.value.toLowerCase();
    const temp = this.props.data;
    const isMatch = (item) => {
      const value = item[column];
      return (getText(value).toLowerCase().indexOf(filterText) != -1);
    }

    const filteredItems = temp.filter(item => isMatch(item));
    if (!filteredItems.length) {
      filteredItems.push({ id: "", name: "", address: "", state: "", zip: ""});
    }

    this.setState({
      items: filteredItems,
      filter: filterText
    });
  }

  handleSort(column) {
    let current = this.state.sortDir;
    const sortDir = current[column];
    const getText = this.getTextFormatter(column);

    let sortedItems = _.sortBy(this.state.items, row => getText(row[column]));
    if (sortDir === ASC) {
      sortedItems.reverse();
    }

    // Reset all column sorts before we set the new sort direction below.
    current = this.initSortCols();

    // Reverse sort
    if (sortDir === ASC) {
      current[column] = DESC;
    } else {
      current[column] = ASC;
    }

    // Set state with new sorted order.
    this.setState({
      sortDir: current,
      items: sortedItems,
    });
 }

  render() {
    const { columns, data, showColumnFilter, downloadButton, maxHeight, ...props } = this.props;
    const { items } = this.state;

    return (

            { columns
              .filter(column => !column.hidden)
              .map((column) =>
                     <Column
                       width={200}
                       allowCellsRecycling={false}
                       key={column.id}
                       flexGrow={1}
                       columnKey={column.id}

                       header={

                           {column.name}

                       }

                       cell={
                         (props) => {
                           const row = items[props.rowIndex];
                           let text = row[column.id];
                           if (column.render) {
                             text = column.render(text, row, column.id);
                           }
                           return (
                             <Cell {...props}>
                               {text}
                             </Cell>
                           )
                         }
                       }
                     />
                    )
            }

        {
          downloadButton.map((type) => <Download columns={columns} className={'btn btn-default'} key={type} type={type} items={items} /> )
        }

    );
  }
}

// See https://facebook.github.io/react/docs/typechecking-with-proptypes.html
FlyBaseDataGrid.propTypes = {
  data: PropTypes.array.isRequired,
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      getText: PropTypes.func,
      hidden: PropTypes.bool,
      id: PropTypes.string,
      name: PropTypes.string,
      render: PropTypes.func,
  }),
  ).isRequired,
  rowHeight: PropTypes.number,
  headerHeight: PropTypes.number,
  width: PropTypes.number,
  height: PropTypes.number,
  maxHeight: PropTypes.number,
  showFilter: PropTypes.bool,
};

FlyBaseDataGrid.defaultProps = {
  rowHeight: 50,
  headerHeight: 50,
  width: 1000,
  showFilter: false,
};

export default FlyBaseDataGrid;
<commit_msg>Fix bug with sorting numeric column<commit_after>import React, { Component, PropTypes } from 'react';
import { Table, Column, Cell } from 'fixed-data-table-2';
import _ from 'underscore';

// import '../dist/fixed-data-table.css';
// import '../dist/agr.css';

import { ASC, DESC, NONE } from './constants';
import Header from './components/Header';
import Download from './components/Download';
import Filter from './components/ColumnFilter';
import Dimensions from 'react-dimensions';
import debug from 'debug';

class FlyBaseDataGrid extends Component {
  constructor(props) {
    super(props);
    this.state = {
      sortDir: this.initSortCols(),
      items: props.data,
      filter: '',
    };
    this.handleSort   = this.handleSort.bind(this);
    this.handleColumnFilter = this.handleColumnFilter.bind(this);
  }

  initSortCols() {
    const sortCols = {};
    this.props.columns.forEach((col) => sortCols[col.id] = NONE);
    return sortCols;
  }

  getColumnText(columnId) {
    const columnDef = _.find(this.props.columns, c => c.id === columnId);
    return (columnDef && columnDef.getText) || (v => v);
  }

  handleColumnFilter(e, column){
    const getText = this.getColumnText(column);
    //  reset all column filter boxes to "" on focus change
    //  reset grid to default items
    const filterText = e.target.value.toLowerCase();
    const temp = this.props.data;
    const isMatch = (item) => {
      const value = item[column];
      return (getText(value).toString().toLowerCase().indexOf(filterText) != -1);
    }

    const filteredItems = temp.filter(item => isMatch(item));
    if (!filteredItems.length) {
      filteredItems.push({ id: "", name: "", address: "", state: "", zip: ""});
    }

    this.setState({
      items: filteredItems,
      filter: filterText
    });
  }

  handleSort(column) {
    let current = this.state.sortDir;
    const sortDir = current[column];
    const getText = this.getColumnText(column);

    let sortedItems = _.sortBy(this.state.items, row => getText(row[column]));
    if (sortDir === ASC) {
      sortedItems.reverse();
    }

    // Reset all column sorts before we set the new sort direction below.
    current = this.initSortCols();

    // Reverse sort
    if (sortDir === ASC) {
      current[column] = DESC;
    } else {
      current[column] = ASC;
    }

    // Set state with new sorted order.
    this.setState({
      sortDir: current,
      items: sortedItems,
    });
 }

  render() {
    const { columns, data, showColumnFilter, downloadButton, maxHeight, ...props } = this.props;
    const { items } = this.state;

    return (

            { columns
              .filter(column => !column.hidden)
              .map((column) =>
                     <Column
                       width={200}
                       allowCellsRecycling={false}
                       key={column.id}
                       flexGrow={1}
                       columnKey={column.id}

                       header={

                           {column.name}

                       }

                       cell={
                         (props) => {
                           const row = items[props.rowIndex];
                           let text = row[column.id];
                           if (column.render) {
                             text = column.render(text, row, column.id);
                           }
                           return (
                             <Cell {...props}>
                               {text}
                             </Cell>
                           )
                         }
                       }
                     />
                    )
            }

        {
          downloadButton.map((type) => <Download columns={columns} className={'btn btn-default'} key={type} type={type} items={items} /> )
        }

    );
  }
}

// See https://facebook.github.io/react/docs/typechecking-with-proptypes.html
FlyBaseDataGrid.propTypes = {
  data: PropTypes.array.isRequired,
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      getText: PropTypes.func,
      hidden: PropTypes.bool,
      id: PropTypes.string,
      name: PropTypes.string,
      render: PropTypes.func,
  }),
  ).isRequired,
  rowHeight: PropTypes.number,
  headerHeight: PropTypes.number,
  width: PropTypes.number,
  height: PropTypes.number,
  maxHeight: PropTypes.number,
  showFilter: PropTypes.bool,
};

FlyBaseDataGrid.defaultProps = {
  rowHeight: 50,
  headerHeight: 50,
  width: 1000,
  showFilter: false,
};

export default FlyBaseDataGrid;
<|endoftext|>


<commit_before>(function () {

    var config = {
        arrays: [
            {
                title: "map",
                body: function () {
                    /*
R.map(function(x) { return x + 1; }, [10, 20, 30]);
                     */
                }
            },
            {
                title: 'filter',
                body: function() {
                    /*
R.filter(function(x) { return x > 10; }, [10, 20, 3, 100, 33, 1]);
                     */
                }
            },
            {
                title: 'reduce',
                body: function() {
                    /*
R.reduce(function(acc, x) { return acc + x; }, 0, [10, 20, 30, 40]);
                     */
                }
            },
            {
                title: 'reduceRight',
                body: function() {
                    /*
R.reduceRight(function(acc, x) { return acc + x; }, '', ['s', 'd', 'r', 'a', 'w', 'k', 'c', 'a', 'b']);
                     */
                }
            },
            {
                title: 'transduce',
                body: function() {
                    /*
var priceOrZero = R.compose(R.defaultTo(0), R.prop('price'));
var purchases = [
  {price: 30},
  {price: 0},
  {notPrice: 'cow'},
  {price: 10},
  {price: 6},
  {}
];
R.transduce(R.map(priceOrZero), R.add, 0, purchases);
                     */
                }
            }
        ],
        functions: [
            {
                title: "curry",
                body: function () {
                    /*
var f = R.curry(function(a, b, c) { return b + (a * c); });
f(2)(3)(4);
                    */
                }
            },
            {
                title: "compose",
                body: function () {
                    /*
var composeEx = R.compose(
            R.map(function(x) { return x * x; }),
            R.filter(function(x) { return x > 4; })
            );
composeEx([2, 3, 4, 5]);
                     */
                }
            },
            {
                title: "pipe",
                body: function () {
                    /*
var pipeEx = R.pipe(
            R.map(function(x) { return x * x; }),
            R.filter(function(x) { return x > 4; })
            );
pipeEx([2, 3, 4, 5]);
                     */
                }
            },
            {
                title: 'useWith',
                body: function() {
                    /*
var sum = function() {
    return R.reduce(function(acc, x) { return acc + x; }, 0, arguments);
};
var useWithEx = R.useWith(
    sum,
    function(y) { return y * 2; },
    function(x) { return x * x; }
);
useWithEx(2, 3, 4);
                     */
                }
            },
            {
                title: 'converge',
                body: function() {
                    /*
var add = function(a, b) { return a + b; };
var multiply = function(a, b) { return a * b; };
var subtract = function(a, b) { return a - b; };
var convergeEx = R.converge(multiply, add, subtract)
convergeEx(2, 4);
                     */
                }
            }
        ],
        objects: [
            {
                title: "prop",
                body: function () {
                    /*
var propEx = R.prop('x');
propEx({w: 'WWW', x: 'XXX', z: 'ZZZ'});
                    */
                }
            }
        ],
        fantasy: [
            {
                title: 'Futures',
                body: function(){
                    /*
var log = console.log.bind(console);
var error = console.error.bind(console);

var url = 'http://reqr.es/api/users?page=3';

var fetch = function(url) {
    return new Future(function(rej, res){
        var oReq = new XMLHttpRequest();
        oReq.addEventListener("load", res, false);
        oReq.addEventListener("error", rej, false);
        oReq.addEventListener("abort", rej, false);
        oReq.open("get", url, true);
        oReq.send();
    });
};

var f = R.compose(R.map(S.parseJson), R.map(R.path(['target','response'])), fetch)(url);

f.fork(error, R.map(log)); //=> Object {page: "3", per_page: 3, total: 12, total_pages: 4, data: Array[3]}
                     */
                }
            }
        ]
    };

    var lists = Object.keys(config);
    var examples = document.getElementById("examples");
    var elms = {};
    var cache = {};

    //build lists
    lists.forEach(function (name) {
        elms[name] = document.getElementById( name );
        config[name].forEach(function (e, i) {
            var str = e.body
                .toString()
                .replace(/[^\*]+\*/m, "")
                .split("*/")[0]
                .split("\n")
                .filter(function (s) {
                    return !!s && !s.match(/^\s+$/);
                })
                .join("\n");

            cache[e.title] = str;
            $('
' + e.title + '
' ).appendTo(elms[name]);
        });
    });

    //add handlers
    $("button[type=reset]" ).on("click", function () {
        window.location.href = window.location.href.split("#")[0];
    });

    $(examples).on("click", function (e) {
        // here's that nasty global var:
        cm.setValue( cache[e.target.textContent] );
        e.preventDefault();
    });

}());

<commit_msg>update example<commit_after>(function () {

    var config = {
        arrays: [
            {
                title: "map",
                body: function () {
                    /*
R.map(function(x) { return x + 1; }, [10, 20, 30]);
                     */
                }
            },
            {
                title: 'filter',
                body: function() {
                    /*
R.filter(function(x) { return x > 10; }, [10, 20, 3, 100, 33, 1]);
                     */
                }
            },
            {
                title: 'reduce',
                body: function() {
                    /*
R.reduce(function(acc, x) { return acc + x; }, 0, [10, 20, 30, 40]);
                     */
                }
            },
            {
                title: 'reduceRight',
                body: function() {
                    /*
R.reduceRight(function(acc, x) { return acc + x; }, '', ['s', 'd', 'r', 'a', 'w', 'k', 'c', 'a', 'b']);
                     */
                }
            },
            {
                title: 'transduce',
                body: function() {
                    /*
var priceOrZero = R.compose(R.defaultTo(0), R.prop('price'));
var purchases = [
  {price: 30},
  {price: 0},
  {notPrice: 'cow'},
  {price: 10},
  {price: 6},
  {}
];
R.transduce(R.map(priceOrZero), R.add, 0, purchases);
                     */
                }
            }
        ],
        functions: [
            {
                title: "curry",
                body: function () {
                    /*
var f = R.curry(function(a, b, c) { return b + (a * c); });
f(2)(3)(4);
                    */
                }
            },
            {
                title: "compose",
                body: function () {
                    /*
var composeEx = R.compose(
            R.map(function(x) { return x * x; }),
            R.filter(function(x) { return x > 4; })
            );
composeEx([2, 3, 4, 5]);
                     */
                }
            },
            {
                title: "pipe",
                body: function () {
                    /*
var pipeEx = R.pipe(
            R.map(function(x) { return x * x; }),
            R.filter(function(x) { return x > 4; })
            );
pipeEx([2, 3, 4, 5]);
                     */
                }
            },
            {
                title: 'useWith',
                body: function() {
                    /*
var sum = function() {
    return R.reduce(function(acc, x) { return acc + x; }, 0, arguments);
};
var useWithEx = R.useWith(
    sum,
    function(y) { return y * 2; },
    function(x) { return x * x; }
);
useWithEx(2, 3, 4);
                     */
                }
            },
            {
                title: 'converge',
                body: function() {
                    /*
var add = function(a, b) { return a + b; };
var multiply = function(a, b) { return a * b; };
var subtract = function(a, b) { return a - b; };
var convergeEx = R.converge(multiply, add, subtract)
convergeEx(2, 4);
                     */
                }
            }
        ],
        objects: [
            {
                title: "prop",
                body: function () {
                    /*
var propEx = R.prop('x');
propEx({w: 'WWW', x: 'XXX', z: 'ZZZ'});
                    */
                }
            }
        ],
        fantasy: [
            {
                title: 'Futures',
                body: function(){
                    /*
var log = console.log.bind(console);
var error = console.error.bind(console);

var url = 'http://reqr.es/api/users?page=3';

var xhr = function(url) {
    return new Future(function(rej, res){
        var oReq = new XMLHttpRequest();
        oReq.addEventListener("load", res, false);
        oReq.addEventListener("error", rej, false);
        oReq.addEventListener("abort", rej, false);
        oReq.open("get", url, true);
        oReq.send();
    });
};

var f = R.compose(R.map(S.parseJson), R.map(R.path(['target','response'])), xhr)(url);

f.fork(error, R.map(log)); //=> Object {page: "3", per_page: 3, total: 12, total_pages: 4, data: Array[3]}
                     */
                }
            }
        ]
    };

    var lists = Object.keys(config);
    var examples = document.getElementById("examples");
    var elms = {};
    var cache = {};

    //build lists
    lists.forEach(function (name) {
        elms[name] = document.getElementById( name );
        config[name].forEach(function (e, i) {
            var str = e.body
                .toString()
                .replace(/[^\*]+\*/m, "")
                .split("*/")[0]
                .split("\n")
                .filter(function (s) {
                    return !!s && !s.match(/^\s+$/);
                })
                .join("\n");

            cache[e.title] = str;
            $('
' + e.title + '
' ).appendTo(elms[name]);
        });
    });

    //add handlers
    $("button[type=reset]" ).on("click", function () {
        window.location.href = window.location.href.split("#")[0];
    });

    $(examples).on("click", function (e) {
        // here's that nasty global var:
        cm.setValue( cache[e.target.textContent] );
        e.preventDefault();
    });

}());

<|endoftext|>


<commit_before>function Search(){
	// Run function to erase previous searched results.
	Delete_Prev_Search();
	var search_url = "https://www.mangaeden.com/api/list/0/";
	var search_text = document.getElementById("search_m").value;
	// Ajax request
	var xhttp = new XMLHttpRequest();
	xhttp.onreadystatechange = function() {
		if (xhttp.readyState == 4 && xhttp.status == 200) {
			var myArr = JSON.parse(xhttp.responseText);
			var img_url = "https://cdn.mangaeden.com/mangasimg/";
			for(var key in myArr["manga"]){
				var result = myArr["manga"][key]["a"].indexOf(search_text) > -1;
				if (result == true){
					// Print the searched image.
					var img = new Image();
					var div = document.getElementById('pics');
					if (myArr["manga"][key]["im"] != null){
						// Main Div file.
						var Main_Div = document.createElement("div");
						var CreateA = document.createElement("a");
						CreateA.href = "pages/manga.html?fname=Rothshi&id=" + myArr["manga"][key]["i"];
						Main_Div.className = "wrapper";

						img.src = img_url + myArr["manga"][key]["im"];
						// Create the thumbnail for each images.
						img.className = "img-thumbnail";
						CreateA.appendChild(img);

						// Create div for the description.
						var divDesc = document.createElement("p");
						divDesc.innerHTML = myArr["manga"][key]["t"].slice(0, 20); // Name of the Manga
						divDesc.className = "desc_content";
						CreateA.appendChild(divDesc)
						Main_Div.appendChild(CreateA);
						// Insert the images inside the main div picture element.
						div.appendChild(Main_Div);
					}
				}
			}
		}
	};
	xhttp.open("GET", search_url, true);
	xhttp.send();
}

function Delete_Prev_Search(){
	// Hide the front page element.
	$("#front").empty();

	// Erase everything in the pics element.
	$("#pics").empty();
}

function Search_M(){
	window.location.replace("../index.html");
}

function Random_manga(){
	var search_url = "https://www.mangaeden.com/api/list/0/";
	// Ajax request
	var xhttp = new XMLHttpRequest();
	xhttp.onreadystatechange = function() {
		if (xhttp.readyState == 4 && xhttp.status == 200) {
			var myArr = JSON.parse(xhttp.responseText);
			var img_url = "https://cdn.mangaeden.com/mangasimg/";
			var count = 1;
			var div = document.getElementById('random_pics');
			while (count <= 5){
				var img = new Image();
				var key = Math.floor(Math.random() * 16950) + 1;
				// Main Div file.
				var Main_Div = document.createElement("div");
				var CreateA = document.createElement("a");
				CreateA.href = "pages/manga.html?fname=Rothshi&id=" + myArr["manga"][key]["i"];
				Main_Div.className = "wrapper";
				img.src = img_url + myArr["manga"][key]["im"];
				// Create the thumbnail for each images.
				img.className = "img-thumbnail";
				CreateA.appendChild(img);
				// Create div for the description.
				var divDesc = document.createElement("p");
				divDesc.innerHTML = myArr["manga"][key]["t"].slice(0, 20); // Name of the Manga
				divDesc.className = "desc_content";
				CreateA.appendChild(divDesc)
				Main_Div.appendChild(CreateA);
				// Insert the images inside the main div picture element.
				div.appendChild(Main_Div);
				count = count + 1;
			}
		}
	};
	xhttp.open("GET", search_url, true);
	xhttp.send();

}
<commit_msg>Update search.js<commit_after>function Search(mod){
	// Run function to erase previous searched results.
	Delete_Prev_Search();
	var search_url = "https://www.mangaeden.com/api/list/0/";
	var search_text = document.getElementById("search_m").value;
	// Ajax request
	var xhttp = new XMLHttpRequest();
	xhttp.onreadystatechange = function() {
		if (xhttp.readyState == 4 && xhttp.status == 200) {
			var myArr = JSON.parse(xhttp.responseText);
			var img_url = "https://cdn.mangaeden.com/mangasimg/";
			for(var key in myArr["manga"]){
				var result = myArr["manga"][key]["a"].indexOf(search_text) > -1;
				if (result == true){
					// Print the searched image.
					var img = new Image();
					var div = document.getElementById('pics');
					if (myArr["manga"][key]["im"] != null){
						// Main Div file.
						var Main_Div = document.createElement("div");
						var CreateA = document.createElement("a");
						if (mod == "index"){
							CreateA.href = "pages/manga.html?fname=Rothshi&id=" + myArr["manga"][key]["i"];
						}
						else if (mod == "other"){
							CreateA.href = "manga.html?fname=Rothshi&id=" + myArr["manga"][key]["i"];
						}
						Main_Div.className = "wrapper";

						img.src = img_url + myArr["manga"][key]["im"];
						// Create the thumbnail for each images.
						img.className = "img-thumbnail";
						CreateA.appendChild(img);

						// Create div for the description.
						var divDesc = document.createElement("p");
						divDesc.innerHTML = myArr["manga"][key]["t"].slice(0, 20); // Name of the Manga
						divDesc.className = "desc_content";
						CreateA.appendChild(divDesc)
						Main_Div.appendChild(CreateA);
						// Insert the images inside the main div picture element.
						div.appendChild(Main_Div);
					}
				}
			}
		}
	};
	xhttp.open("GET", search_url, true);
	xhttp.send();
}

function Delete_Prev_Search(){
	// Hide the front page element.
	$("#front").empty();

	// Erase everything in the pics element.
	$("#pics").empty();
}

function Search_M(){
	window.location.replace("../index.html");
}

function Random_manga(){
	var search_url = "https://www.mangaeden.com/api/list/0/";
	// Ajax request
	var xhttp = new XMLHttpRequest();
	xhttp.onreadystatechange = function() {
		if (xhttp.readyState == 4 && xhttp.status == 200) {
			var myArr = JSON.parse(xhttp.responseText);
			var img_url = "https://cdn.mangaeden.com/mangasimg/";
			var count = 1;
			var div = document.getElementById('random_pics');
			while (count <= 5){
				var img = new Image();
				var key = Math.floor(Math.random() * 16950) + 1;
				// Main Div file.
				var Main_Div = document.createElement("div");
				var CreateA = document.createElement("a");
				CreateA.href = "pages/manga.html?fname=Rothshi&id=" + myArr["manga"][key]["i"];
				Main_Div.className = "wrapper";
				img.src = img_url + myArr["manga"][key]["im"];
				// Create the thumbnail for each images.
				img.className = "img-thumbnail";
				CreateA.appendChild(img);
				// Create div for the description.
				var divDesc = document.createElement("p");
				divDesc.innerHTML = myArr["manga"][key]["t"].slice(0, 20); // Name of the Manga
				divDesc.className = "desc_content";
				CreateA.appendChild(divDesc)
				Main_Div.appendChild(CreateA);
				// Insert the images inside the main div picture element.
				div.appendChild(Main_Div);
				count = count + 1;
			}
		}
	};
	xhttp.open("GET", search_url, true);
	xhttp.send();

}
<|endoftext|>


<commit_before>var color = d3.scale.category10();
var parseDate = d3.time.format('%Y-%m-%d').parse;
var parsePhoneDate = d3.time.format('%b %e, %Y').parse;
var lineData = [];
var phonearenaMarkers = [];
var finished = false;
var phonearenaCount = 0;

var sources = [
    'https://www.quandl.com/api/v1/datasets/GOOG/NASDAQ_AAPL.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-12-31',
    'https://www.quandl.com/api/v1/datasets/GOOG/NYSE_MSI.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-12-31',
    'https://www.quandl.com/api/v1/datasets/GOOG/NASDAQ_GOOG.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-12-31',
    'https://www.quandl.com/api/v1/datasets/GOOG/NYSE_SNE.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-12-31',
    '../phone-data/Apple.json',
    '../phone-data/Google.json',
    '../phone-data/Motorola.json',
    '../phone-data/Sony.json'
];

var retrieveSingleStockInfo = function (index, callback) {
    d3.json(sources[index], function (error, stockJson) {
        if (stockJson.code) {
            lineData[index] = {
                values: stockJson.data.map(function (data) {
                    return ({
                        date: parseDate(data[0]), // date
                        open: data[1], // open price
                        close: data[4], // close price
                        percentChange: (((data[4] - data[1]) / data[1]) * 100)
                    });
                }),
                key: stockJson.code,
                color: color(stockJson.code)
            };
        } else {
            phonearenaMarkers = phonearenaMarkers.concat(stockJson.map(function (d) {
                return {
                    value: parsePhoneDate(d.announceDate), // some dates will be out of view
                    text: d.name,
                    class: d.symbol + "-marker"
                    //stockSymbol: d.symbol
                    // imageUrl
                    // ratings
                    // release date?
                };
            }));
            phonearenaCount++;
        }

        // After last request, make the graph because we have all the JSON requests have returned
        callback();
    });
};

var fetchJSON = function (callback) {
    for (var index = 0; index < sources.length; index++) {
        retrieveSingleStockInfo(index, callback);
    }
};

fetchJSON(function () {
    // output data to JSON
    if (lineData.length == 4 &&
            lineData[0] != null &&
            lineData[1] != null &&
            lineData[2] != null &&
            lineData[3] != null &&
            !finished) {
        buildTimeSeries();
        finished = true;
    }
});

buildTimeSeries = function () {
    // REWORK THIS IF TIME PERMITS ---
    var modData = [];
    var xValues = [];

    lineData.forEach(function (d) {
        var item = [d.key];
        var xDate = [d.key + 'x'];
        d.values.forEach(function (j) {
            item.push(j.percentChange);
            xDate.push(j.date);
        });

        modData.push(item);
        xValues.push(xDate);
    });

    /* NOW TO ACTUALLY MAKE THE CHART */

    //for (i=0; i<modData.length; i++) {
    //    console.log(modData[i]);
    //}

    var chart = c3.generate({
        bindto: '#chart',
        size: {
            height: 650
        },
        data: {
            xs: {
                "NASDAQ_AAPL": "NASDAQ_AAPLx",
                "NYSE_MSI": "NYSE_MSIx",
                "NASDAQ_GOOG": "NASDAQ_GOOGx",
                "NYSE_SNE": "NYSE_SNEx"
            },
            columns: [
                modData[0],
                modData[1],
                modData[2],
                modData[3],
                xValues[0],
                xValues[1],
                xValues[2],
                xValues[3]

            ],
            types: {
                NASDAQ_AAPL: 'area-spline',
                NYSE_MSI: 'area-spline',
                NASDAQ_GOOG: 'area-spline',
                NYSE_SNE: 'area-spline'
                // 'line', 'spline', 'step', 'area', 'area-step' are also available to stack
            },
            //groups: [
            //],
            names: {
                // Rename legend values
                NASDAQ_AAPL: 'Apple',
                NYSE_MSI: 'Motorola',
                NASDAQ_GOOG: 'Google',
                NYSE_SNE: 'Sony'
            }
        },
        axis: {
            x: {
                type: 'timeseries',
                tick: {
                    format: '%Y-%m-%d'
                }
            },
            y: {
                label: {
                    text: 'Percent Change (Collapsed Weekly)',
                    position: 'outer-middle'
                }
            }
        },
        grid: {
            x: {
                lines: phonearenaMarkers
            }
        },
        legend: {
            show: true
        },
        zoom: {
            enabled: true
        },
        subchart: {
            show: true
        }
    });

    updateTimeSeriesMarkers = function (value, checked) {
        if (checked) {
            d3.selectAll("." + value + "-marker").attr('display', 'inline');
        } else {
            d3.selectAll("." + value + "-marker").attr('display', 'none');
        }
    };
};<commit_msg>fix the date on stacked graph<commit_after>var color = d3.scale.category10();
var parseDate = d3.time.format('%Y-%m-%d').parse;
var parsePhoneDate = d3.time.format('%b %e, %Y').parse;
var lineData = [];
var phonearenaMarkers = [];
var finished = false;
var phonearenaCount = 0;

var sources = [
    'https://www.quandl.com/api/v1/datasets/GOOG/NASDAQ_AAPL.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-04-22',
    'https://www.quandl.com/api/v1/datasets/GOOG/NYSE_MSI.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-04-22',
    'https://www.quandl.com/api/v1/datasets/GOOG/NASDAQ_GOOG.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-04-22',
    'https://www.quandl.com/api/v1/datasets/GOOG/NYSE_SNE.json?auth_token=MX4jg7jQAHp7SDTxGxd4&collapse=monthly&trim_start=2012-04-22',
    '../phone-data/Apple.json',
    '../phone-data/Google.json',
    '../phone-data/Motorola.json',
    '../phone-data/Sony.json'
];

var retrieveSingleStockInfo = function (index, callback) {
    d3.json(sources[index], function (error, stockJson) {
        if (stockJson.code) {
            lineData[index] = {
                values: stockJson.data.map(function (data) {
                    return ({
                        date: parseDate(data[0]), // date
                        open: data[1], // open price
                        close: data[4], // close price
                        percentChange: (((data[4] - data[1]) / data[1]) * 100)
                    });
                }),
                key: stockJson.code,
                color: color(stockJson.code)
            };
        } else {
            phonearenaMarkers = phonearenaMarkers.concat(stockJson.map(function (d) {
                return {
                    value: parsePhoneDate(d.announceDate), // some dates will be out of view
                    text: d.name,
                    class: d.symbol + "-marker"
                    //stockSymbol: d.symbol
                    // imageUrl
                    // ratings
                    // release date?
                };
            }));
            phonearenaCount++;
        }

        // After last request, make the graph because we have all the JSON requests have returned
        callback();
    });
};

var fetchJSON = function (callback) {
    for (var index = 0; index < sources.length; index++) {
        retrieveSingleStockInfo(index, callback);
    }
};

fetchJSON(function () {
    // output data to JSON
    if (lineData.length == 4 &&
            lineData[0] != null &&
            lineData[1] != null &&
            lineData[2] != null &&
            lineData[3] != null &&
            !finished) {
        buildTimeSeries();
        finished = true;
    }
});

buildTimeSeries = function () {
    // REWORK THIS IF TIME PERMITS ---
    var modData = [];
    var xValues = [];

    lineData.forEach(function (d) {
        var item = [d.key];
        var xDate = [d.key + 'x'];
        d.values.forEach(function (j) {
            item.push(j.percentChange);
            xDate.push(j.date);
        });

        modData.push(item);
        xValues.push(xDate);
    });

    /* NOW TO ACTUALLY MAKE THE CHART */

    //for (i=0; i<modData.length; i++) {
    //    console.log(modData[i]);
    //}

    var chart = c3.generate({
        bindto: '#chart',
        size: {
            height: 650
        },
        data: {
            xs: {
                "NASDAQ_AAPL": "NASDAQ_AAPLx",
                "NYSE_MSI": "NYSE_MSIx",
                "NASDAQ_GOOG": "NASDAQ_GOOGx",
                "NYSE_SNE": "NYSE_SNEx"
            },
            columns: [
                modData[0],
                modData[1],
                modData[2],
                modData[3],
                xValues[0],
                xValues[1],
                xValues[2],
                xValues[3]

            ],
            types: {
                NASDAQ_AAPL: 'area-spline',
                NYSE_MSI: 'area-spline',
                NASDAQ_GOOG: 'area-spline',
                NYSE_SNE: 'area-spline'
                // 'line', 'spline', 'step', 'area', 'area-step' are also available to stack
            },
            //groups: [
            //],
            names: {
                // Rename legend values
                NASDAQ_AAPL: 'Apple',
                NYSE_MSI: 'Motorola',
                NASDAQ_GOOG: 'Google',
                NYSE_SNE: 'Sony'
            }
        },
        axis: {
            x: {
                type: 'timeseries',
                tick: {
                    format: '%Y-%m-%d'
                }
            },
            y: {
                label: {
                    text: 'Percent Change (Collapsed Weekly)',
                    position: 'outer-middle'
                }
            }
        },
        grid: {
            x: {
                lines: phonearenaMarkers
            }
        },
        legend: {
            show: true
        },
        zoom: {
            enabled: true
        },
        subchart: {
            show: true
        }
    });

    updateTimeSeriesMarkers = function (value, checked) {
        if (checked) {
            d3.selectAll("." + value + "-marker").attr('display', 'inline');
        } else {
            d3.selectAll("." + value + "-marker").attr('display', 'none');
        }
    };
};<|endoftext|>


<commit_before>// An subclass of CodeMirror which adds a few methods that make it easier
// to work with character indexes rather than {line, ch} objects.
function IndexableCodeMirror(place, givenOptions) {
  var codeMirror = CodeMirror(place, givenOptions);

  // This is the reverse of CodeMirror2's coordsFromIndex() method.
  codeMirror.indexFromCoords = function(pos) {
    var index = pos.ch;
    for (var i = 0; i < pos.line; i++)
      index += codeMirror.getLine(i).length + 1;
    return index;
  };

  // Returns the character index of the cursor position.
  codeMirror.getCursorIndex = function() {
    return codeMirror.indexFromCoords(codeMirror.getCursor());
  };

  return codeMirror;
}

// A subclass of IndexableCodeMirror which continuously re-parses
// the code in its editor. Also adds a Backbone.Events interface
// for extension points to hook into.
function ParsingCodeMirror(place, givenOptions) {
  // Called whenever content of the editor area changes.
  function reparse() {
    var sourceCode = codeMirror.getValue();
    var result = givenOptions.parse(sourceCode);
    codeMirror.trigger("reparse", {
      error: result.error,
      sourceCode: sourceCode,
      document: result.document
    });
    // Cursor activity would've been fired before us, so call it again
    // to make sure it displays the right context-sensitive help based
    // on the new state of the document.
    onCursorActivity();
  }

  // Called whenever the user moves their cursor in the editor area.
  function onCursorActivity() {
    codeMirror.trigger("cursor-activity");
  }

  // The number of milliseconds to wait before re-parsing the editor
  // content.
  var parseDelay = givenOptions.parseDelay || 300;
  var reparseTimeout;

  givenOptions.onChange = function() {
    clearTimeout(reparseTimeout);
    reparseTimeout = setTimeout(reparse, parseDelay);
  };
  givenOptions.onCursorActivity = onCursorActivity;

  var codeMirror = IndexableCodeMirror(place, givenOptions);
  _.extend(codeMirror, Backbone.Events);
  codeMirror.reparse = reparse;
  return codeMirror;
}

// This helper class keeps track of different kinds of highlighting in
// a CodeMirror instance.
function MarkTracker(codeMirror) {
  var classNames = {};
  var marks = [];

  return {
    // Mark a given start/end interval in the CodeMirror, based on character
    // indices (not {line, ch} objects), with the given class name. If
    // an element is provided, give it the class name too.
    mark: function(start, end, className, element) {
      if (!(className in classNames))
        classNames[className] = [];
      if (element) {
        classNames[className].push(element);
        $(element).addClass(className);
      }
      start = codeMirror.coordsFromIndex(start);
      end = codeMirror.coordsFromIndex(end);
      marks.push(codeMirror.markText(start, end, className));
    },
    // Clear all marks made so far and remove the class from any elements
    // it was previously given to.
    clear: function() {
      marks.forEach(function(mark) {
        // Odd, from the CodeMirror docs you'd think this would remove
        // the class from the highlighted text, too, but it doesn't.
        // I guess we're just garbage collecting here.
        mark.clear();
      });
      var wrapper = codeMirror.getWrapperElement();
      for (var className in classNames) {
        classNames[className].forEach(function(element) {
          $(element).removeClass(className);
        });
        $("." + className, wrapper).removeClass(className);
      }

      marks = [];
      classNames = {};
    }
  };
}

// Provides context-sensitive help for a ParsingCodeMirror based on
// the current cursor position.
function ContextSensitiveHelp(options) {
  var self = {};
  var codeMirror = options.codeMirror;
  var templates = options.templates;
  var helpArea = options.helpArea;
  var helpIndex = options.helpIndex;
  var lastEvent = null;

  // Keep track of context-sensitive help highlighting.
  var cursorHelpMarks = MarkTracker(codeMirror);

  // Helper function to show how many elements match the currently
  // highlighted selector.
  function makeSelectorMatchesHelp(help) {
    var interval = help.highlights[0];
    var selector = lastEvent.sourceCode.slice(interval.start, interval.end);

    // TODO: Because we're looking at the generated document fragment and
    // not an actual HTML document, implied elements like <body> may not
    // be captured here.
    var matches = lastEvent.document.querySelectorAll(selector).length;
    var matchHelp = templates.find(".selector-matches").clone();
    matchHelp.find(".match-count").text(matches);
    return matchHelp;
  }

  codeMirror.on("reparse", function(event) {
    lastEvent = event;
    helpIndex.clear();
    if (event.error)
      helpArea.hide();
    else
      helpIndex.build(event.document, event.sourceCode);
  });

  codeMirror.on("cursor-activity", function() {
    cursorHelpMarks.clear();
    var help = helpIndex.get(codeMirror.getCursorIndex());
    if (help) {
      helpArea.html(help.html);
      if (help.type == "cssSelector" && !lastEvent.error)
        helpArea.append(makeSelectorMatchesHelp(help));
      var learn = templates.find(".learn-more").clone()
        .attr("href", help.url);
      helpArea.append(learn).show();
      help.highlights.forEach(function(interval) {
        cursorHelpMarks.mark(interval.start, interval.end,
                             "cursor-help-highlight");
      });
    } else
      helpArea.hide();
  });

  return self;
}

// Provides helpful Slowparse-based error suggestions for a
// ParsingCodeMirror.
function ErrorHelp(options) {
  var self = {};
  var codeMirror = options.codeMirror;
  var errorArea = options.errorArea;

  // Keep track of error highlighting.
  var errorHelpMarks = MarkTracker(codeMirror);

  // Report the given Slowparse error.
  function reportError(error) {
    errorArea.fillError(error).eachErrorHighlight(function(start, end, i) {
      errorHelpMarks.mark(start, end, "highlight-" + (i+1), this);
    });
  }

  codeMirror.on("reparse", function(event) {
    errorHelpMarks.clear();
    if (event.error)
      reportError(event.error);
    else
      errorArea.hide();
  });
  return self;
}

// Displays the HTML source of a CodeMirror editor as a rendered preview
// in an iframe.
function LivePreview(options) {
  var self = {};

  options.codeMirror.on("reparse", function(event) {
    if (!event.error || options.ignoreErrors) {
      // Update the preview area with the given HTML.
      var doc = options.previewArea.contents()[0];
      var wind = doc.defaultView;
      var x = wind.pageXOffset;
      var y = wind.pageYOffset;

      doc.open();
      doc.write(event.sourceCode);
      doc.close();

      // TODO: If the document has images that take a while to load
      // and the previous scroll position of the document depends on
      // their dimensions being set on load, we may need to refresh
      // this scroll position after the document has loaded.
      wind.scroll(x, y);
    }
  });

  return self;
}

function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split("&");
  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split("=");
    if (pair[0] == variable) {
      return unescape(pair[1]);
    }
  }
}

$(window).load(function() {
  jQuery.loadErrors("slowparse/spec/", ["base", "forbidjs"], function() {
    var codeMirror = ParsingCodeMirror($("#source")[0], {
      mode: "text/html",
      theme: "jsbin",
      tabMode: "indent",
      lineWrapping: true,
      lineNumbers: true,
      value: $("#initial-html").text().trim(),
      parse: function(html) {
        return Slowparse.HTML(document, html, [TreeInspectors.forbidJS]);
      }
    });
    var cursorHelp = ContextSensitiveHelp({
      codeMirror: codeMirror,
      helpIndex: Help.Index(),
      templates: $("#templates"),
      helpArea: $(".help")
    });
    var errorHelp = ErrorHelp({
      codeMirror: codeMirror,
      errorArea: $(".error")
    });
    var preview = LivePreview({
      codeMirror: codeMirror,
      ignoreErrors: true,
      previewArea: $("#preview")
    });
    var publishUI = PublishUI({
      codeMirror: codeMirror,
      publisher: Publisher("http://wpm.toolness.org"),
      dialog: $("#publish-dialog")
    });
    codeMirror.reparse();
    codeMirror.focus();

    $("#undo").click(function() { codeMirror.undo(); });
    $("#redo").click(function() { codeMirror.redo(); });
    $("#publish").click(function() { publishUI.saveCode(); });

    if (getQueryVariable('p'))
      publishUI.loadCode(getQueryVariable('p'));

    // We're only exposing the editor as a global so we can debug via
    // the console. Other parts of our code should never reference this.
    window._codeMirror = codeMirror;
  });
});
<commit_msg>simplified code for makeSelectorMatchesHelp() function.<commit_after>// An subclass of CodeMirror which adds a few methods that make it easier
// to work with character indexes rather than {line, ch} objects.
function IndexableCodeMirror(place, givenOptions) {
  var codeMirror = CodeMirror(place, givenOptions);

  // This is the reverse of CodeMirror2's coordsFromIndex() method.
  codeMirror.indexFromCoords = function(pos) {
    var index = pos.ch;
    for (var i = 0; i < pos.line; i++)
      index += codeMirror.getLine(i).length + 1;
    return index;
  };

  // Returns the character index of the cursor position.
  codeMirror.getCursorIndex = function() {
    return codeMirror.indexFromCoords(codeMirror.getCursor());
  };

  return codeMirror;
}

// A subclass of IndexableCodeMirror which continuously re-parses
// the code in its editor. Also adds a Backbone.Events interface
// for extension points to hook into.
function ParsingCodeMirror(place, givenOptions) {
  // Called whenever content of the editor area changes.
  function reparse() {
    var sourceCode = codeMirror.getValue();
    var result = givenOptions.parse(sourceCode);
    codeMirror.trigger("reparse", {
      error: result.error,
      sourceCode: sourceCode,
      document: result.document
    });
    // Cursor activity would've been fired before us, so call it again
    // to make sure it displays the right context-sensitive help based
    // on the new state of the document.
    onCursorActivity();
  }

  // Called whenever the user moves their cursor in the editor area.
  function onCursorActivity() {
    codeMirror.trigger("cursor-activity");
  }

  // The number of milliseconds to wait before re-parsing the editor
  // content.
  var parseDelay = givenOptions.parseDelay || 300;
  var reparseTimeout;

  givenOptions.onChange = function() {
    clearTimeout(reparseTimeout);
    reparseTimeout = setTimeout(reparse, parseDelay);
  };
  givenOptions.onCursorActivity = onCursorActivity;

  var codeMirror = IndexableCodeMirror(place, givenOptions);
  _.extend(codeMirror, Backbone.Events);
  codeMirror.reparse = reparse;
  return codeMirror;
}

// This helper class keeps track of different kinds of highlighting in
// a CodeMirror instance.
function MarkTracker(codeMirror) {
  var classNames = {};
  var marks = [];

  return {
    // Mark a given start/end interval in the CodeMirror, based on character
    // indices (not {line, ch} objects), with the given class name. If
    // an element is provided, give it the class name too.
    mark: function(start, end, className, element) {
      if (!(className in classNames))
        classNames[className] = [];
      if (element) {
        classNames[className].push(element);
        $(element).addClass(className);
      }
      start = codeMirror.coordsFromIndex(start);
      end = codeMirror.coordsFromIndex(end);
      marks.push(codeMirror.markText(start, end, className));
    },
    // Clear all marks made so far and remove the class from any elements
    // it was previously given to.
    clear: function() {
      marks.forEach(function(mark) {
        // Odd, from the CodeMirror docs you'd think this would remove
        // the class from the highlighted text, too, but it doesn't.
        // I guess we're just garbage collecting here.
        mark.clear();
      });
      var wrapper = codeMirror.getWrapperElement();
      for (var className in classNames) {
        classNames[className].forEach(function(element) {
          $(element).removeClass(className);
        });
        $("." + className, wrapper).removeClass(className);
      }

      marks = [];
      classNames = {};
    }
  };
}

// Provides context-sensitive help for a ParsingCodeMirror based on
// the current cursor position.
function ContextSensitiveHelp(options) {
  var self = {};
  var codeMirror = options.codeMirror;
  var templates = options.templates;
  var helpArea = options.helpArea;
  var helpIndex = options.helpIndex;
  var lastEvent = null;

  // Keep track of context-sensitive help highlighting.
  var cursorHelpMarks = MarkTracker(codeMirror);

  // Helper function to show how many elements match the currently
  // highlighted selector.
  function makeSelectorMatchesHelp(help) {
    // TODO: Because we're looking at the generated document fragment and
    // not an actual HTML document, implied elements like <body> may not
    // be captured here.
    var selector = help.highlights[0].value;
    var matches = lastEvent.document.querySelectorAll(selector).length;
    var matchHelp = templates.find(".selector-matches").clone();
    matchHelp.find(".match-count").text(matches);
    return matchHelp;
  }

  codeMirror.on("reparse", function(event) {
    lastEvent = event;
    helpIndex.clear();
    if (event.error)
      helpArea.hide();
    else
      helpIndex.build(event.document, event.sourceCode);
  });

  codeMirror.on("cursor-activity", function() {
    cursorHelpMarks.clear();
    var help = helpIndex.get(codeMirror.getCursorIndex());
    if (help) {
      helpArea.html(help.html);
      if (help.type == "cssSelector" && !lastEvent.error)
        helpArea.append(makeSelectorMatchesHelp(help));
      var learn = templates.find(".learn-more").clone()
        .attr("href", help.url);
      helpArea.append(learn).show();
      help.highlights.forEach(function(interval) {
        cursorHelpMarks.mark(interval.start, interval.end,
                             "cursor-help-highlight");
      });
    } else
      helpArea.hide();
  });

  return self;
}

// Provides helpful Slowparse-based error suggestions for a
// ParsingCodeMirror.
function ErrorHelp(options) {
  var self = {};
  var codeMirror = options.codeMirror;
  var errorArea = options.errorArea;

  // Keep track of error highlighting.
  var errorHelpMarks = MarkTracker(codeMirror);

  // Report the given Slowparse error.
  function reportError(error) {
    errorArea.fillError(error).eachErrorHighlight(function(start, end, i) {
      errorHelpMarks.mark(start, end, "highlight-" + (i+1), this);
    });
  }

  codeMirror.on("reparse", function(event) {
    errorHelpMarks.clear();
    if (event.error)
      reportError(event.error);
    else
      errorArea.hide();
  });
  return self;
}

// Displays the HTML source of a CodeMirror editor as a rendered preview
// in an iframe.
function LivePreview(options) {
  var self = {};

  options.codeMirror.on("reparse", function(event) {
    if (!event.error || options.ignoreErrors) {
      // Update the preview area with the given HTML.
      var doc = options.previewArea.contents()[0];
      var wind = doc.defaultView;
      var x = wind.pageXOffset;
      var y = wind.pageYOffset;

      doc.open();
      doc.write(event.sourceCode);
      doc.close();

      // TODO: If the document has images that take a while to load
      // and the previous scroll position of the document depends on
      // their dimensions being set on load, we may need to refresh
      // this scroll position after the document has loaded.
      wind.scroll(x, y);
    }
  });

  return self;
}

function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split("&");
  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split("=");
    if (pair[0] == variable) {
      return unescape(pair[1]);
    }
  }
}

$(window).load(function() {
  jQuery.loadErrors("slowparse/spec/", ["base", "forbidjs"], function() {
    var codeMirror = ParsingCodeMirror($("#source")[0], {
      mode: "text/html",
      theme: "jsbin",
      tabMode: "indent",
      lineWrapping: true,
      lineNumbers: true,
      value: $("#initial-html").text().trim(),
      parse: function(html) {
        return Slowparse.HTML(document, html, [TreeInspectors.forbidJS]);
      }
    });
    var cursorHelp = ContextSensitiveHelp({
      codeMirror: codeMirror,
      helpIndex: Help.Index(),
      templates: $("#templates"),
      helpArea: $(".help")
    });
    var errorHelp = ErrorHelp({
      codeMirror: codeMirror,
      errorArea: $(".error")
    });
    var preview = LivePreview({
      codeMirror: codeMirror,
      ignoreErrors: true,
      previewArea: $("#preview")
    });
    var publishUI = PublishUI({
      codeMirror: codeMirror,
      publisher: Publisher("http://wpm.toolness.org"),
      dialog: $("#publish-dialog")
    });
    codeMirror.reparse();
    codeMirror.focus();

    $("#undo").click(function() { codeMirror.undo(); });
    $("#redo").click(function() { codeMirror.redo(); });
    $("#publish").click(function() { publishUI.saveCode(); });

    if (getQueryVariable('p'))
      publishUI.loadCode(getQueryVariable('p'));

    // We're only exposing the editor as a global so we can debug via
    // the console. Other parts of our code should never reference this.
    window._codeMirror = codeMirror;
  });
});
<|endoftext|>