<commit_before>'use strict'

const request = require('request')
const { Parser } = require('inspire-parser')
const { pick, defaults } = require('lodash')
const { pipe, pipeline } = require('mississippi')
const Harvester = require('./harvester')
const { expandRecord } = require('./records')
const { detectNewCompatibilityOption, applyCompatibilityOptions } = require('./compatibility')
const stringstream = require('stringstream')
const EventEmitter = require('events').EventEmitter
const { createGunzip } = require('zlib')
const { version } = require('../package.json')
const debugRequest = require('debug')('csw-client:request')
const debugInfo = require('debug')('csw-client:info')

class ExceptionReport extends Error {}

function parseResponse(reqResponse) {
  return new Promise((resolve, reject) => {
    const { headers } = reqResponse
    const gzip = headers['content-encoding'] && headers['content-encoding'].includes('gzip')
    const decompressedResponse = gzip ?
      pipeline(reqResponse, createGunzip()) :
      reqResponse

    const xmlParser = new Parser()

    xmlParser.on('result', result => {
      if (result.type === 'ExceptionReport') {
        const exception = result.body.exceptionReport
        const err = new ExceptionReport(exception.exceptionCode)
        err.exception = exception
        reject(err)
      } else {
        resolve(result)
      }
    })

    pipe(
      decompressedResponse,
      stringstream('utf8'),
      xmlParser,
      function (err) {
        if (err) return reject(err)
        reject(new Error('No parsed content'))
      }
    )
  })
}

class Client extends EventEmitter {

  constructor(serviceUrl, options = {}) {
    if (!serviceUrl) throw new Error('serviceUrl is required!')
    super()
    this.serviceUrl = serviceUrl
    this.userAgent = options.userAgent || `csw-client/${version}`
    this.compatibilityOptions = options.compatibilityOptions || {}
    this.timeout = options.timeout || 10000
    this.gzip = options.gzip !== false

    this.hopCount = 0

    debugInfo('created client for service %s', this.serviceUrl)
  }

  request(query) {
    if (this.hopCount >= 5) {
      debugInfo('followed too many redirections')
      return Promise.reject('Followed too many redirections')
    }

    return new Promise((resolve, reject) => {
      const req = request({
        url: this.serviceUrl,
        qs: Object.assign({}, query, { service: 'CSW', version: '2.0.2' }),
        qsStringifyOptions: { encode: !this.compatibilityOptions.noEncodeQs },
        headers: {
          'User-Agent': this.userAgent,
        },
        agentOptions: this.agentOptions,
        timeout: this.timeout,
        gzip: this.gzip,
        followRedirect: false,
      })

      req
        .on('error', reject)
        .on('response', response => {
          if (response.statusCode === 301 || response.statusCode === 302) {
            response.destroy()
            const redirectUrl = response.headers.location
            const startQsPos = redirectUrl.indexOf('?')
            this.serviceUrl = redirectUrl.substr(0, startQsPos)
            this.hopCount++
            debugInfo('following service redirection => %s - hop: %d', this.serviceUrl, this.hopCount)
            resolve(this.request(query))
          }
          if (response.statusCode >= 400) {
            response.destroy()
            reject(new Error('Responded with an error status code: ' + response.statusCode))
          }
          if (! response.headers['content-type'] || response.headers['content-type'].indexOf('xml') === -1) {
            response.destroy()
            reject(new Error('Not an XML response'))
          }

          response.pause()
          resolve(response)
        })

      debugRequest('request: %s', decodeURIComponent(req.url.href))
      this.emit('request', req)
    })
  }

  getCapabilities() {
    return this.request({ request: 'GetCapabilities' })
  }

  async capabilities() {
    const response = await this.getCapabilities()
    const result = await parseResponse(response)
    if (result.type !== 'Capabilities') {
      throw new Error('Not acceptable response type: ' + result.type)
    }
    return result.body
  }

  getRecords(options = {}) {
    if (options.schema === 'inspire') {
      options.typeNames = 'gmd:MD_Metadata'
      options.outputSchema = 'http://www.isotc211.org/2005/gmd'
    }
    const params =  pick(options,
      'maxRecords',
      'startPosition',
      'typeNames',
      'outputSchema',
      'elementSetName',
      'resultType',
      'namespace',
      'constraintLanguage')
    defaults(params, {
      resultType: 'results',
      elementSetName: 'full',
      typeNames: 'csw:Record',
      maxRecords: 10,
    })
    params.request = 'GetRecords'
    return this.request(params)
  }

  async records(options = {}) {
    const computedOptions = this.computeOptions(options)

    const fetchRecords = async (fetchOptions) => {
      const response = await this.getRecords(fetchOptions)
      const result = await parseResponse(response)
      if (result.type !== 'GetRecordsResponse') {
        throw new Error('Not acceptable response type: ' + result.type)
      }
      return {
        records: (result.body.searchResults.children || []).map(expandRecord),
        matched: parseInt(result.body.searchResults.numberOfRecordsMatched),
        returned: parseInt(result.body.searchResults.numberOfRecordsReturned),
      }
    }

    try {
      if (computedOptions.progressiveElementSetName) {
        computedOptions.elementSetName = 'brief'
        const { records, matched, returned } = await fetchRecords(computedOptions)
        const recordOptions = Object.assign({}, computedOptions, { elementSetName: 'full' })
        return {
          records: await Promise.all(records.map(record => this.record(record.id, recordOptions))),
          matched,
          returned,
        }
      } else {
        return await fetchRecords(computedOptions)
      }
    } catch (err) {
      if (err instanceof ExceptionReport) {
        const shouldRetry = detectNewCompatibilityOption(this, err.exception)
        if (shouldRetry) return this.records(options)
      }
      throw err
    }
  }

  computeOptions(options = {}) {
    return applyCompatibilityOptions(Object.assign({}, this.compatibilityOptions, options))
  }

  async count(options = {}) {
    const result = await this.records(Object.assign({}, options, { resultType: 'hits' }))
    if (result.matched >= 0) return result.matched
    throw new Error('Invalid count result')
  }

  getRecordById(id, options = {}) {
    if (options.schema === 'inspire') {
      options.typeNames = 'gmd:MD_Metadata'
      options.outputSchema = 'http://www.isotc211.org/2005/gmd'
    }
    const params =  pick(options,
      'typeNames',
      'outputSchema',
      'elementSetName',
      'namespace')
    defaults(params, {
      elementSetName: 'full',
      typeNames: 'csw:Record',
    })
    params.request = 'GetRecordById'
    params.id = id
    return this.request(params)
  }

  async record(id, options = {}) {
    const response = await this.getRecordById(id, options)
    const result = await parseResponse(response)
    if (result.type !== 'GetRecordByIdResponse') {
      throw new Error('Not acceptable response type: ' + result.type)
    }
    if (!result.body.children) {
      throw new Error('No record returned for id: ' + id)
    }
    return expandRecord(result.body.children[0])
  }

  /* Legacy */

  harvest(options) {
    return new Harvester(this, options)
  }

}

module.exports = Client
<commit_msg>Add read timeout on each request<commit_after>'use strict'

const request = require('request')
const { Parser } = require('inspire-parser')
const { pick, defaults } = require('lodash')
const { pipe, pipeline, through } = require('mississippi')
const Harvester = require('./harvester')
const { expandRecord } = require('./records')
const { detectNewCompatibilityOption, applyCompatibilityOptions } = require('./compatibility')
const stringstream = require('stringstream')
const EventEmitter = require('events').EventEmitter
const { createGunzip } = require('zlib')
const { version } = require('../package.json')
const debugRequest = require('debug')('csw-client:request')
const debugInfo = require('debug')('csw-client:info')

class ExceptionReport extends Error {}

function parseResponse(reqResponse) {
  return new Promise((resolve, reject) => {
    const { headers } = reqResponse
    const gzip = headers['content-encoding'] && headers['content-encoding'].includes('gzip')
    const decompressedResponse = gzip ?
      pipeline(reqResponse, createGunzip()) :
      reqResponse

    // Read timeout
    const readTimeout = 20000
    let _readTimeout

    function clearReadTimeout() {
      if (_readTimeout) clearTimeout(_readTimeout)
    }

    function renewReadTimeout() {
      clearReadTimeout()
      _readTimeout = setTimeout(function () {
        reject(new Error('Response read timeout'))
      }, readTimeout)
    }

    const readTimeoutStream = through(
      function (chunk, enc, cb) {
        renewReadTimeout()
        cb(null, chunk)
      },
      function (cb) {
        clearReadTimeout()
        cb()
      }
    )

    // XML Parser
    const xmlParser = new Parser()

    xmlParser.on('result', result => {
      if (result.type === 'ExceptionReport') {
        const exception = result.body.exceptionReport
        const err = new ExceptionReport(exception.exceptionCode)
        err.exception = exception
        reject(err)
      } else {
        resolve(result)
      }
    })

    pipe(
      decompressedResponse,
      readTimeoutStream,
      stringstream('utf8'),
      xmlParser,
      function (err) {
        if (err) return reject(err)
        reject(new Error('No parsed content'))
      }
    )
  })
}

class Client extends EventEmitter {

  constructor(serviceUrl, options = {}) {
    if (!serviceUrl) throw new Error('serviceUrl is required!')
    super()
    this.serviceUrl = serviceUrl
    this.userAgent = options.userAgent || `csw-client/${version}`
    this.compatibilityOptions = options.compatibilityOptions || {}
    this.timeout = options.timeout || 10000
    this.gzip = options.gzip !== false

    this.hopCount = 0

    debugInfo('created client for service %s', this.serviceUrl)
  }

  request(query) {
    if (this.hopCount >= 5) {
      debugInfo('followed too many redirections')
      return Promise.reject('Followed too many redirections')
    }

    return new Promise((resolve, reject) => {
      const req = request({
        url: this.serviceUrl,
        qs: Object.assign({}, query, { service: 'CSW', version: '2.0.2' }),
        qsStringifyOptions: { encode: !this.compatibilityOptions.noEncodeQs },
        headers: {
          'User-Agent': this.userAgent,
        },
        agentOptions: this.agentOptions,
        timeout: this.timeout,
        gzip: this.gzip,
        followRedirect: false,
      })

      req
        .on('error', reject)
        .on('response', response => {
          if (response.statusCode === 301 || response.statusCode === 302) {
            response.destroy()
            const redirectUrl = response.headers.location
            const startQsPos = redirectUrl.indexOf('?')
            this.serviceUrl = redirectUrl.substr(0, startQsPos)
            this.hopCount++
            debugInfo('following service redirection => %s - hop: %d', this.serviceUrl, this.hopCount)
            resolve(this.request(query))
          }
          if (response.statusCode >= 400) {
            response.destroy()
            reject(new Error('Responded with an error status code: ' + response.statusCode))
          }
          if (! response.headers['content-type'] || response.headers['content-type'].indexOf('xml') === -1) {
            response.destroy()
            reject(new Error('Not an XML response'))
          }

          response.pause()
          resolve(response)
        })

      debugRequest('request: %s', decodeURIComponent(req.url.href))
      this.emit('request', req)
    })
  }

  getCapabilities() {
    return this.request({ request: 'GetCapabilities' })
  }

  async capabilities() {
    const response = await this.getCapabilities()
    const result = await parseResponse(response)
    if (result.type !== 'Capabilities') {
      throw new Error('Not acceptable response type: ' + result.type)
    }
    return result.body
  }

  getRecords(options = {}) {
    if (options.schema === 'inspire') {
      options.typeNames = 'gmd:MD_Metadata'
      options.outputSchema = 'http://www.isotc211.org/2005/gmd'
    }
    const params =  pick(options,
      'maxRecords',
      'startPosition',
      'typeNames',
      'outputSchema',
      'elementSetName',
      'resultType',
      'namespace',
      'constraintLanguage')
    defaults(params, {
      resultType: 'results',
      elementSetName: 'full',
      typeNames: 'csw:Record',
      maxRecords: 10,
    })
    params.request = 'GetRecords'
    return this.request(params)
  }

  async records(options = {}) {
    const computedOptions = this.computeOptions(options)

    const fetchRecords = async (fetchOptions) => {
      const response = await this.getRecords(fetchOptions)
      const result = await parseResponse(response)
      if (result.type !== 'GetRecordsResponse') {
        throw new Error('Not acceptable response type: ' + result.type)
      }
      return {
        records: (result.body.searchResults.children || []).map(expandRecord),
        matched: parseInt(result.body.searchResults.numberOfRecordsMatched),
        returned: parseInt(result.body.searchResults.numberOfRecordsReturned),
      }
    }

    try {
      if (computedOptions.progressiveElementSetName) {
        computedOptions.elementSetName = 'brief'
        const { records, matched, returned } = await fetchRecords(computedOptions)
        const recordOptions = Object.assign({}, computedOptions, { elementSetName: 'full' })
        return {
          records: await Promise.all(records.map(record => this.record(record.id, recordOptions))),
          matched,
          returned,
        }
      } else {
        return await fetchRecords(computedOptions)
      }
    } catch (err) {
      if (err instanceof ExceptionReport) {
        const shouldRetry = detectNewCompatibilityOption(this, err.exception)
        if (shouldRetry) return this.records(options)
      }
      throw err
    }
  }

  computeOptions(options = {}) {
    return applyCompatibilityOptions(Object.assign({}, this.compatibilityOptions, options))
  }

  async count(options = {}) {
    const result = await this.records(Object.assign({}, options, { resultType: 'hits' }))
    if (result.matched >= 0) return result.matched
    throw new Error('Invalid count result')
  }

  getRecordById(id, options = {}) {
    if (options.schema === 'inspire') {
      options.typeNames = 'gmd:MD_Metadata'
      options.outputSchema = 'http://www.isotc211.org/2005/gmd'
    }
    const params =  pick(options,
      'typeNames',
      'outputSchema',
      'elementSetName',
      'namespace')
    defaults(params, {
      elementSetName: 'full',
      typeNames: 'csw:Record',
    })
    params.request = 'GetRecordById'
    params.id = id
    return this.request(params)
  }

  async record(id, options = {}) {
    const response = await this.getRecordById(id, options)
    const result = await parseResponse(response)
    if (result.type !== 'GetRecordByIdResponse') {
      throw new Error('Not acceptable response type: ' + result.type)
    }
    if (!result.body.children) {
      throw new Error('No record returned for id: ' + id)
    }
    return expandRecord(result.body.children[0])
  }

  /* Legacy */

  harvest(options) {
    return new Harvester(this, options)
  }

}

module.exports = Client
<|endoftext|>


<commit_before>;(function ($, window) {

  var defaults = {
        checkbox : {
          spriteWidth          : 20,
          spriteHeight         : 20,
          spriteUnits          : 'px',
          spriteOrder          : ['uncheckedMouseOut', 'uncheckedMouseOver', 'checkedMouseOut', 'checkedMouseOver'],
          spriteDirection      : 'vertical',
          pathToSprite         : 'images/checkbox_vertical.png',
          labelToThe           : 'right',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-checkbox-wrapper',
          htmlAnchorClass      : 'lazy-checkbox-anchor',
          htmlLabelClass       : 'lazy-checkbox-label',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        },
        radio : {
          spriteWidth          : 20,
          spriteHeight         : 20,
          spriteUnits          : 'px',
          spriteOrder          : ['uncheckedMouseOut', 'uncheckedMouseOver', 'checkedMouseOut', 'checkedMouseOver'],
          spriteDirection      : 'vertical',
          pathToSprite         : 'images/radio_vertical.png',
          labelToThe           : 'right',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-radio-wrapper',
          htmlAnchorClass      : 'lazy-radio-anchor',
          htmlLabelClass       : 'lazy-radio-label',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        },
        button : {
          spriteLeftWidth      : 5,
          spriteLeftHeight     : 30,
          spriteMidWidth       : 1,
          spriteMidHeight      : 30,
          spriteRightWidth     : 5,
          spriteRightHeight    : 30,
          spriteUnits          : 'px',
          spriteOrder          : ['mouseOut', 'mouseOver'],
          spriteDirection      : 'vertical',
          pathToLeftSprite     : 'images/button_left_vertical.png',
          pathToMidSprite      : 'images/button_mid_vertical.png',
          pathToRightSprite    : 'images/button_right_vertical.png',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-button-wrapper',
          htmlAnchorClass      : 'lazy-button-anchor',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        },
        select : {
          spriteLeftWidth      : 5,
          spriteLeftHeight     : 30,
          spriteMidWidth       : 1,
          spriteMidHeight      : 30,
          spriteRightWidth     : 29,
          spriteRightHeight    : 30,
          spriteUnits          : 'px',
          spriteOrder          : ['mouseOut', 'mouseOver'],
          spriteDirection      : 'vertical',
          pathToLeftSprite     : 'images/select_left_vertical.png',
          pathToMidSprite      : 'images/select_mid_vertical.png',
          pathToRightSprite    : 'images/select_right_vertical.png',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-select-wrapper',
          htmlAnchorClass      : 'lazy-select-anchor',
          htmlSpanClass        : 'lazy-select-span',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        }
      },
      pluginName = 'lazyForms',
      document = window.document;

  /**
   * Constructor
   */

  function LazyForms (element, options) {
    this.element    = element;
    this.$element   = $(element);
    this.tag        = this.$element.prop('tagName').toLowerCase();
    this.attrName   = this.$element.attr('name');
    this.options    = $.extend( {}, defaults, options );
    this._defaults  = defaults;
    this._name      = pluginName;
    if (this.tag == 'input') {
      this.attrType = this.$element.attr('type');
    };

    this.init();
  };

  /**
   * Private members
   */

  var _myPrivateVar;

  /**
   * Private methods
   */

  var _myPrivateMethod = function () {

  };

  /**
   * Public methods
   */

  LazyForms.prototype.init = function () {

    this.rebind = function () {
      this.unbind();
      this.bind();
    };

    this.bind = function () {
      // TODO: Allow ability to unbind (destroy?) the plugin
    };

    this.unbind = function () {
      // TODO: Allow ability to rebind the plugin
    };

    this.injectWrapper = function () {
      if ( this.options.htmlWrapperTag && typeof( this.options.htmlWrapperTag ) == 'string' ) {
        this.rawHtmlWrapperTag = this.options.htmlWrapperTag.replace(/[^a-zA-Z]/g, '').toLowerCase();
        this.$element.wrap( '<' + this.rawHtmlWrapperTag + '/>' );
        this.setWrapper();
        this.setWrapperClass();
        //this.styleWrapper();
      };
    };

    this.setWrapperClass = function () {
      if ( this.options.htmlWrapperClass && typeof(this.options.htmlWrapperClass) == 'string' ) {
        this.$wrapper.addClass( this.options.htmlWrapperClass );
      };
    };

    this.setWrapper = function () {
      this.$wrapper = this.$element.parent( this.rawHtmlWrapperTag );
    };

    this.styleWrapper = function () {
      this.$wrapper.css({
        'display' : 'inline-block'
      });
    };

    this.injectLabel = function () {
      this.$wrapper.append( '<label/>' );
      this.setLabel();
      this.setLabelClass();
      if ( this.$element.attr('data-label') ) {
        this.$label.html( this.$element.attr('data-label') );
      };
    };

    this.setLabel = function () {
      this.$label = this.$element.siblings('label');
    };

    this.setLabelClass = function () {
      if ( this.options.htmlLabelClass && typeof( this.options.htmlLabelClass ) == 'string' ) {
        this.$label.addClass( this.options.htmlLabelClass );
      };
    };

    this.injectAnchor = function () {
      this.$wrapper.append('<a href="#"/>');
      this.setAnchor();
      this.setAnchorClass();
      this.styleAnchor();
    };

    this.setAnchor = function () {
      this.$anchor = this.$element.siblings('a');
    };

    this.setAnchorClass = function () {
      if ( this.options.htmlAnchorClass && typeof(this.options.htmlAnchorClass) == 'string' ) {
        this.$anchor.addClass( this.options.htmlAnchorClass );
      };
    };

    this.styleAnchor = function () {
      if ( this.attrType == 'checkbox' ) {
        if ( this.options.pathToCheckboxSprite && typeof( this.options.pathToCheckboxSprite ) == 'string' ) {
          this.pathToSprite = this.options.pathToCheckboxSprite;
        };
      } else if ( this.attrType == 'radio' ) {
        if ( this.options.pathToRadioSprite && typeof( this.options.pathToRadioSprite ) == 'string' ) {
          this.pathToSprite = this.options.pathToRadioSprite;
        };
      };

      this.$anchor.css({
        'background-image' : 'url(' + this.pathToSprite + ')',
        'background-repeat': 'no-repeat no-repeat'
      });

      this.$anchor.css({
        'height' : this.rawSpriteHeight + this.options.spriteUnits,
        'width'  : this.rawSpriteWidth + this.options.spriteUnits,
        'display': 'block'
      });

      if ( this.options.labelToThe && typeof( this.options.labelToThe ) == 'string' ) {
        if ( this.options.labelToThe == 'right' ) {
          this.$anchor.css({
            'float' : 'left'
          });
        } else if ( this.options.labelToThe == 'left' ) {
          this.$anchor.css({
            'float' : 'right'
          });
        };
      };
    };

    this.hideElement = function () {
      this.$element.hide();
    };

    this.computeSprite = function () {
      if ( this.options.spriteOrder && typeof( this.options.spriteOrder ) == 'object' ) {
        this.spriteLength = this.options.spriteOrder.length;
        this.spriteStates = {};
        if ( this.options.spriteHeight ) {
          if ( typeof( this.options.spriteHeight ) == 'string' ) {
            this.rawSpriteHeight = parseInt( this.options.spriteHeight );
          } else if ( typeof( this.options.spriteHeight ) == 'number' ) {
            this.rawSpriteHeight = this.options.spriteHeight;
          };
        };
        if ( this.options.spriteWidth ) {
          if ( typeof( this.options.spriteWidth ) == 'string' ) {
            this.rawSpriteHeight = parseInt( this.options.spriteWidth );
          } else if ( typeof( this.options.spriteWidth ) == 'number' ) {
            this.rawSpriteWidth = this.options.spriteWidth;
          };
        };
        for ( var i = 0; i < this.spriteLength; i++ ) {
          if ( this.options.spriteDirection == 'vertical' ) {
            this.spriteStates[ this.options.spriteOrder[ i ] ] = '0' + this.options.spriteUnits + ' ' + ((this.rawSpriteHeight * i) * -1).toString() + this.options.spriteUnits;
          } else if ( this.options.spriteDirection == 'horizontal' ) {
            this.spriteStates[ this.options.spriteOrder[ i ] ] = ((this.rawSpriteWidth * i) * -1).toString() + this.options.spriteUnits + ' 0' + this.options.spriteUnits;
          };
        };
      };
    };

    this.customBeforeClick = function ( event ) {
      if ( this.options.onBeforeClick ) {
        this.options.onBeforeClick( this, event );
      };
    };

    this.customAfterClick = function ( event ) {
      if ( this.options.onAfterClick ) {
        this.options.onAfterClick( this, event );
      };
    };

    this.customBeforeMouseOver = function ( event ) {
      if ( this.options.onBeforeMouseOver ) {
        this.options.onBeforeMouseOver( this, event );
      };
    };

    this.customAfterMouseOver = function ( event ) {
      if ( this.options.onAfterMouseOver ) {
        this.options.onAfterMouseOver( this, event );
      };
    };

    this.customBeforeMouseOut = function ( event ) {
      if ( this.options.onBeforeMouseOut ) {
        this.options.onBeforeMouseOut( this, event );
      };
    };

    this.customAfterMouseOut = function ( event ) {
      if ( this.options.onAfterMouseOut ) {
        this.options.onAfterMouseOut( this, event );
      };
    };

    this.resetGroup = function () {
      $( this.tag + '[type="' + this.attrType + '"][name="' + this.attrName + '"]' ).attr( 'checked', false );
      $( this.tag + '[type="' + this.attrType + '"][name="' + this.attrName + '"]' ).siblings( this.$label ).css({
        'background-position' : this.spriteStates.uncheckedMouseOut
      });
    };

    this.radioClick = function ( event ) {
      this.resetGroup();
      this.$element.attr( 'checked', true );
      this.$anchor.css({
        'background-position' : this.spriteStates.checkedMouseOut
      });
    };

    this.checkboxClickOn = function ( event ) {
      event.preventDefault();
      this.customBeforeClick( event );
      this.$element.attr( 'checked', !this.$element.attr('checked') );
      this.$anchor.css({
        'background-position' : this.spriteStates.checkedMouseOut
      });
      this.customAfterClick( event );
    };

    this.checkboxClickOff = function ( event ) {
      event.preventDefault();
      this.customBeforeClick( event );
      this.$element.attr( 'checked', !this.$element.attr('checked') );
      this.$anchor.css({
        'background-position' : this.spriteStates.uncheckedMouseOut
      });
      this.customAfterClick( event );
    };

    this.mouseOut = function ( event ) {
      this.customBeforeMouseOut( event );
      if ( this.$element.attr('checked') == 'checked' ) {
        this.$anchor.css({
          'background-position' : this.spriteStates.checkedMouseOut
        });
      } else {
        this.$anchor.css({
          'background-position' : this.spriteStates.uncheckedMouseOut
        });
      };
      this.customAfterMouseOut( event );
    };

    this.mouseOver = function ( event ) {
      this.customBeforeMouseOver( event );
      if ( this.$element.attr('checked') == 'checked' ) {
        this.$anchor.css({
          'background-position' : this.spriteStates.checkedMouseOver
        });
      } else {
        this.$anchor.css({
          'background-position' : this.spriteStates.uncheckedMouseOver
        });
      };
      this.customAfterMouseOver( event );
    };

    this.bindEvents = function () {
      var self = this;

      if ( this.attrType == 'checkbox' ) {
        this.$wrapper.toggle( function ( event ) {
          self.checkboxClickOn( event );
        }, function ( event ) {
          self.checkboxClickOff( event );
        });
      } else if ( this.attrType == 'radio' ) {
        this.$wrapper.bind( 'click', function (event) {
          self.radioClick( event );
        });
      };

      this.$wrapper.hover( function ( event ) {
        self.mouseOver( event );
      }, function ( event ) {
        self.mouseOut( event );
      });

    };

    this.build = function () {
      this.hideElement();
      this.computeSprite();
      this.injectWrapper();
      this.injectAnchor();
      this.injectLabel();
      this.bindEvents();
    };

    this.build();

  };

  /*
   * Plugin wrapper
   */

  $.fn[pluginName] = function (options) {
    return this.each( function () {
      if ( !$.data( this, 'plugin_' + pluginName ) ) {
        $.data( this, 'plugin_' + pluginName, new LazyForms( this, options ) );
      };
    });
  };

})($, window);<commit_msg>[WIP] Get the wrapper working.<commit_after>;(function ($, window) {

  var defaults = {
        checkbox : {
          spriteWidth          : 20,
          spriteHeight         : 20,
          spriteUnits          : 'px',
          spriteOrder          : ['uncheckedMouseOut', 'uncheckedMouseOver', 'checkedMouseOut', 'checkedMouseOver'],
          spriteDirection      : 'vertical',
          pathToSprite         : 'images/checkbox_vertical.png',
          labelToThe           : 'right',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-checkbox-wrapper',
          htmlAnchorClass      : 'lazy-checkbox-anchor',
          htmlLabelClass       : 'lazy-checkbox-label',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        },
        radio : {
          spriteWidth          : 20,
          spriteHeight         : 20,
          spriteUnits          : 'px',
          spriteOrder          : ['uncheckedMouseOut', 'uncheckedMouseOver', 'checkedMouseOut', 'checkedMouseOver'],
          spriteDirection      : 'vertical',
          pathToSprite         : 'images/radio_vertical.png',
          labelToThe           : 'right',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-radio-wrapper',
          htmlAnchorClass      : 'lazy-radio-anchor',
          htmlLabelClass       : 'lazy-radio-label',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        },
        button : {
          spriteLeftWidth      : 5,
          spriteLeftHeight     : 30,
          spriteMidWidth       : 1,
          spriteMidHeight      : 30,
          spriteRightWidth     : 5,
          spriteRightHeight    : 30,
          spriteUnits          : 'px',
          spriteOrder          : ['mouseOut', 'mouseOver'],
          spriteDirection      : 'vertical',
          pathToLeftSprite     : 'images/button_left_vertical.png',
          pathToMidSprite      : 'images/button_mid_vertical.png',
          pathToRightSprite    : 'images/button_right_vertical.png',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-button-wrapper',
          htmlAnchorClass      : 'lazy-button-anchor',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        },
        select : {
          spriteLeftWidth      : 5,
          spriteLeftHeight     : 30,
          spriteMidWidth       : 1,
          spriteMidHeight      : 30,
          spriteRightWidth     : 29,
          spriteRightHeight    : 30,
          spriteUnits          : 'px',
          spriteOrder          : ['mouseOut', 'mouseOver'],
          spriteDirection      : 'vertical',
          pathToLeftSprite     : 'images/select_left_vertical.png',
          pathToMidSprite      : 'images/select_mid_vertical.png',
          pathToRightSprite    : 'images/select_right_vertical.png',
          htmlWrapperTag       : 'div',
          htmlWrapperClass     : 'lazy-select-wrapper',
          htmlAnchorClass      : 'lazy-select-anchor',
          htmlSpanClass        : 'lazy-select-span',
          onBeforeClick        : false,
          onAfterClick         : false,
          onBeforeMouseOver    : false,
          onAfterMouseOver     : false,
          onBeforeMouseOut     : false,
          onAfterMouseOut      : false
        }
      },
      pluginName = 'lazyForms',
      document = window.document;

  /**
   * Constructor
   */

  function LazyForms (element, options) {
    this.element    = element;
    this.$element   = $(element);
    this.tag        = this.$element.prop('tagName').toLowerCase();
    this.attrName   = this.$element.attr('name');
    this.options    = $.extend( {}, defaults, options );
    this._defaults  = defaults;
    this._name      = pluginName;

    if (this.tag == 'input') {
      this.attrType = this.$element.attr('type');
    };

    if ( this.tag == 'input' ) {
      if ( this.attrType == 'checkbox' ) {
        this.options = this.options.checkbox;
      } else if ( this.attrType == 'radio' ) {
        this.options = this.options.radio;
      } else if ( this.attrType == 'button' || this.attrType == 'submit' ) {
        this.options = this.options.button;
      };
    } else if ( this.tag == 'select' ) {
      this.options = this.options.select;
    } else if ( this.tag == 'button' ) {
      this.options = this.options.button;
    };

    this.init();
  };

  /**
   * Private members
   */

  var _myPrivateVar;

  /**
   * Private methods
   */

  var _myPrivateMethod = function () {

  };

  /**
   * Public methods
   */

  LazyForms.prototype.init = function () {

    this.rebind = function () {
      this.unbind();
      this.bind();
    };

    this.unbind = function () {
      // TODO: Allow ability to unbind (destroy?) the plugin
    };

    this.bind = function () {
      // TODO: Allow ability to bind the plugin (to another element?)
    };

    this.injectWrapper = function () {
      if ( this.options.htmlWrapperTag && typeof( this.options.htmlWrapperTag ) == 'string' ) {
        this.rawHtmlWrapperTag = this.options.htmlWrapperTag.replace(/[^a-zA-Z]/g, '').toLowerCase();
        this.$element.wrap( '<' + this.rawHtmlWrapperTag + '/>' );
        this.setWrapper();
        this.setWrapperClass();
      };
    };

    this.setWrapperClass = function () {
      if ( this.options.htmlWrapperClass && typeof(this.options.htmlWrapperClass) == 'string' ) {
        this.$wrapper.addClass( this.options.htmlWrapperClass );
      };
    };

    this.setWrapper = function () {
      this.$wrapper = this.$element.parent( this.rawHtmlWrapperTag );
    };

    this.styleWrapper = function () {
      this.$wrapper.css({

      });
    };

    this.injectLabel = function () {
      this.$wrapper.append( '<label/>' );
      this.setLabel();
      this.setLabelClass();
      if ( this.$element.attr('data-label') ) {
        this.$label.html( this.$element.attr('data-label') );
      };
    };

    this.setLabel = function () {
      this.$label = this.$element.siblings('label');
    };

    this.setLabelClass = function () {
      if ( this.options.htmlLabelClass && typeof( this.options.htmlLabelClass ) == 'string' ) {
        this.$label.addClass( this.options.htmlLabelClass );
      };
    };

    this.injectAnchor = function () {
      this.$wrapper.append('<a href="#"/>');
      this.setAnchor();
      this.setAnchorClass();
      this.styleAnchor();
    };

    this.setAnchor = function () {
      this.$anchor = this.$element.siblings('a');
    };

    this.setAnchorClass = function () {
      if ( this.options.htmlAnchorClass && typeof(this.options.htmlAnchorClass) == 'string' ) {
        this.$anchor.addClass( this.options.htmlAnchorClass );
      };
    };

    this.styleAnchor = function () {
      if ( this.attrType == 'checkbox' ) {
        if ( this.options.pathToCheckboxSprite && typeof( this.options.pathToCheckboxSprite ) == 'string' ) {
          this.pathToSprite = this.options.pathToCheckboxSprite;
        };
      } else if ( this.attrType == 'radio' ) {
        if ( this.options.pathToRadioSprite && typeof( this.options.pathToRadioSprite ) == 'string' ) {
          this.pathToSprite = this.options.pathToRadioSprite;
        };
      };

      this.$anchor.css({
        'background-image' : 'url(' + this.pathToSprite + ')',
        'background-repeat': 'no-repeat no-repeat'
      });

      this.$anchor.css({
        'height' : this.rawSpriteHeight + this.options.spriteUnits,
        'width'  : this.rawSpriteWidth + this.options.spriteUnits,
        'display': 'block'
      });

      if ( this.options.labelToThe && typeof( this.options.labelToThe ) == 'string' ) {
        if ( this.options.labelToThe == 'right' ) {
          this.$anchor.css({
            'float' : 'left'
          });
        } else if ( this.options.labelToThe == 'left' ) {
          this.$anchor.css({
            'float' : 'right'
          });
        };
      };
    };

    this.hideElement = function () {
      this.$element.hide();
    };

    this.computeSprite = function () {
      if ( this.options.spriteOrder && typeof( this.options.spriteOrder ) == 'object' ) {
        this.spriteLength = this.options.spriteOrder.length;
        this.spriteStates = {};
        if ( this.options.spriteHeight ) {
          if ( typeof( this.options.spriteHeight ) == 'string' ) {
            this.rawSpriteHeight = parseInt( this.options.spriteHeight );
          } else if ( typeof( this.options.spriteHeight ) == 'number' ) {
            this.rawSpriteHeight = this.options.spriteHeight;
          };
        };
        if ( this.options.spriteWidth ) {
          if ( typeof( this.options.spriteWidth ) == 'string' ) {
            this.rawSpriteHeight = parseInt( this.options.spriteWidth );
          } else if ( typeof( this.options.spriteWidth ) == 'number' ) {
            this.rawSpriteWidth = this.options.spriteWidth;
          };
        };
        for ( var i = 0; i < this.spriteLength; i++ ) {
          if ( this.options.spriteDirection == 'vertical' ) {
            this.spriteStates[ this.options.spriteOrder[ i ] ] = '0' + this.options.spriteUnits + ' ' + ((this.rawSpriteHeight * i) * -1).toString() + this.options.spriteUnits;
          } else if ( this.options.spriteDirection == 'horizontal' ) {
            this.spriteStates[ this.options.spriteOrder[ i ] ] = ((this.rawSpriteWidth * i) * -1).toString() + this.options.spriteUnits + ' 0' + this.options.spriteUnits;
          };
        };
      };
    };

    this.customBeforeClick = function ( event ) {
      if ( this.options.onBeforeClick ) {
        this.options.onBeforeClick( this, event );
      };
    };

    this.customAfterClick = function ( event ) {
      if ( this.options.onAfterClick ) {
        this.options.onAfterClick( this, event );
      };
    };

    this.customBeforeMouseOver = function ( event ) {
      if ( this.options.onBeforeMouseOver ) {
        this.options.onBeforeMouseOver( this, event );
      };
    };

    this.customAfterMouseOver = function ( event ) {
      if ( this.options.onAfterMouseOver ) {
        this.options.onAfterMouseOver( this, event );
      };
    };

    this.customBeforeMouseOut = function ( event ) {
      if ( this.options.onBeforeMouseOut ) {
        this.options.onBeforeMouseOut( this, event );
      };
    };

    this.customAfterMouseOut = function ( event ) {
      if ( this.options.onAfterMouseOut ) {
        this.options.onAfterMouseOut( this, event );
      };
    };

    this.resetGroup = function () {
      $( this.tag + '[type="' + this.attrType + '"][name="' + this.attrName + '"]' ).attr( 'checked', false );
      $( this.tag + '[type="' + this.attrType + '"][name="' + this.attrName + '"]' ).siblings( this.$label ).css({
        'background-position' : this.spriteStates.uncheckedMouseOut
      });
    };

    this.radioClick = function ( event ) {
      this.resetGroup();
      this.$element.attr( 'checked', true );
      this.$anchor.css({
        'background-position' : this.spriteStates.checkedMouseOut
      });
    };

    this.checkboxClickOn = function ( event ) {
      event.preventDefault();
      this.customBeforeClick( event );
      this.$element.attr( 'checked', !this.$element.attr('checked') );
      this.$anchor.css({
        'background-position' : this.spriteStates.checkedMouseOut
      });
      this.customAfterClick( event );
    };

    this.checkboxClickOff = function ( event ) {
      event.preventDefault();
      this.customBeforeClick( event );
      this.$element.attr( 'checked', !this.$element.attr('checked') );
      this.$anchor.css({
        'background-position' : this.spriteStates.uncheckedMouseOut
      });
      this.customAfterClick( event );
    };

    this.mouseOut = function ( event ) {
      this.customBeforeMouseOut( event );
      if ( this.$element.attr('checked') == 'checked' ) {
        this.$anchor.css({
          'background-position' : this.spriteStates.checkedMouseOut
        });
      } else {
        this.$anchor.css({
          'background-position' : this.spriteStates.uncheckedMouseOut
        });
      };
      this.customAfterMouseOut( event );
    };

    this.mouseOver = function ( event ) {
      this.customBeforeMouseOver( event );
      if ( this.$element.attr('checked') == 'checked' ) {
        this.$anchor.css({
          'background-position' : this.spriteStates.checkedMouseOver
        });
      } else {
        this.$anchor.css({
          'background-position' : this.spriteStates.uncheckedMouseOver
        });
      };
      this.customAfterMouseOver( event );
    };

    this.bindEvents = function () {
      var self = this;

      if ( this.attrType == 'checkbox' ) {
        this.$wrapper.toggle( function ( event ) {
          self.checkboxClickOn( event );
        }, function ( event ) {
          self.checkboxClickOff( event );
        });
      } else if ( this.attrType == 'radio' ) {
        this.$wrapper.bind( 'click', function (event) {
          self.radioClick( event );
        });
      };

      this.$wrapper.hover( function ( event ) {
        self.mouseOver( event );
      }, function ( event ) {
        self.mouseOut( event );
      });

    };

    this.buildCheckbox = function () {

    };

    this.buildRadio = function () {

    };

    this.buildSelect = function () {

    };

    this.buildButton = function () {

    };

    this.build = function () {
      this.hideElement();
      // this.computeSprite();
      this.injectWrapper();
      //this.injectAnchor();
      // this.injectLabel();
      // this.bindEvents();
    };

    this.build();

  };

  /*
   * Plugin wrapper
   */

  $.fn[pluginName] = function (options) {
    return this.each( function () {
      if ( !$.data( this, 'plugin_' + pluginName ) ) {
        $.data( this, 'plugin_' + pluginName, new LazyForms( this, options ) );
      };
    });
  };

})($, window);<|endoftext|>


<commit_before>var ReplManager = require("./repl");
var Command = require("./command");
var provision = require("truffle-provisioner");
var contract = require("truffle-contract");
var Web3 = require("web3");
var vm = require("vm");
var expect = require("truffle-expect");
var _ = require("lodash");
var TruffleError = require("truffle-error");
var fs = require("fs");
var os = require("os");
var path = require("path");

function Console(tasks, options) {
  expect.options(options, [
    "working_directory",
    "contracts_directory",
    "contracts_build_directory",
    "migrations_directory",
    "network",
    "network_id",
    "provider",
    "resolver",
    "build_directory"
  ]);

  this.options = options;

  this.repl = options.repl || new ReplManager(options);
  this.command = new Command(tasks);

  this.web3 = new Web3();
  this.web3.setProvider(options.provider);
};

Console.prototype.start = function(callback) {
  var self = this;

  if (!this.repl) {
    this.repl = new Repl(this.options);
  }

  // TODO: This should probalby be elsewhere.
  // It's here to ensure the repl manager instance gets
  // passed down to commands.
  this.options.repl = this.repl;

  this.provision(function(err, abstractions) {
    if (err) {
      self.options.logger.log("Unexpected error: Cannot provision contracts while instantiating the console.");
      self.options.logger.log(err.stack || err.message || err);
    }

    self.repl.start({
      prompt: "truffle(" + self.options.network + ")> ",
      context: {
        web3: self.web3,
      },
      interpreter: self.interpret.bind(self),
      done: callback
    });

    self.resetContractsInConsoleContext(abstractions);
  });
};

Console.prototype.provision = function(callback) {
  var self = this;

  fs.readdir(this.options.contracts_build_directory, function(err, files) {
    if (err) {
      // Error reading the build directory? Must mean it doesn't exist or we don't have access to it.
      // Couldn't provision the contracts if we wanted. It's possible we're hiding very rare FS
      // errors, but that's better than showing the user error messages that will be "build folder
      // doesn't exist" 99.9% of the time.
    }

    var promises = [];
    files = files || [];

    files.forEach(function(file) {
      promises.push(new Promise(function(accept, reject) {
        fs.readFile(path.join(self.options.contracts_build_directory, file), "utf8", function(err, body) {
          if (err) return reject(err);
          try {
            body = JSON.parse(body);
          } catch (e) {
            return reject(new Error("Cannot parse " + file + ": " + e.message));
          }

          accept(body);
        })
      }))
    });

    Promise.all(promises).then(function(json_blobs) {
      var abstractions = json_blobs.map(function(json) {
        var abstraction = contract(json);
        provision(abstraction, self.options);
        return abstraction;
      });

      self.resetContractsInConsoleContext(abstractions);

      callback(null, abstractions);
    }).catch(callback);
  });
};

Console.prototype.resetContractsInConsoleContext = function(abstractions) {
  var self = this;

  abstractions = abstractions || []

  var contextVars = {};

  abstractions.forEach(function(abstraction) {
    contextVars[abstraction.contract_name] = abstraction;
  });

  self.repl.setContextVars(contextVars);
}

Console.prototype.interpret = function(cmd, context, filename, callback) {
  var self = this;

  if (this.command.getCommand(cmd.trim()) != null) {
    return self.command.run(cmd.trim(), this.options, function(err) {
      if (err) {
        // Perform error handling ourselves.
        if (err instanceof TruffleError) {
          console.log(err.message);
        } else {
          // Bubble up all other unexpected errors.
          console.log(err.stack || err.toString());
        }
        return callback();
      }

      // Reprovision after each command as it may change contracts.
      self.provision(function(err, abstractions) {
        // Don't pass abstractions to the callback if they're there or else
        // they'll get printed in the repl.
        callback(err);
      });
    });
  }

  var result;
  try {
    result = vm.runInContext(cmd, context, {
      displayErrors: false
    });
  } catch (e) {
    return callback(e);
  }

  // Resolve all promises. This will leave non-promises alone.
  Promise.resolve(result).then(function(res) { callback(null, res) }).catch(callback);
}

module.exports = Console;
<commit_msg>Re-add elusive noAliases arg to console getCommand() check<commit_after>var ReplManager = require("./repl");
var Command = require("./command");
var provision = require("truffle-provisioner");
var contract = require("truffle-contract");
var Web3 = require("web3");
var vm = require("vm");
var expect = require("truffle-expect");
var _ = require("lodash");
var TruffleError = require("truffle-error");
var fs = require("fs");
var os = require("os");
var path = require("path");

function Console(tasks, options) {
  expect.options(options, [
    "working_directory",
    "contracts_directory",
    "contracts_build_directory",
    "migrations_directory",
    "network",
    "network_id",
    "provider",
    "resolver",
    "build_directory"
  ]);

  this.options = options;

  this.repl = options.repl || new ReplManager(options);
  this.command = new Command(tasks);

  this.web3 = new Web3();
  this.web3.setProvider(options.provider);
};

Console.prototype.start = function(callback) {
  var self = this;

  if (!this.repl) {
    this.repl = new Repl(this.options);
  }

  // TODO: This should probalby be elsewhere.
  // It's here to ensure the repl manager instance gets
  // passed down to commands.
  this.options.repl = this.repl;

  this.provision(function(err, abstractions) {
    if (err) {
      self.options.logger.log("Unexpected error: Cannot provision contracts while instantiating the console.");
      self.options.logger.log(err.stack || err.message || err);
    }

    self.repl.start({
      prompt: "truffle(" + self.options.network + ")> ",
      context: {
        web3: self.web3,
      },
      interpreter: self.interpret.bind(self),
      done: callback
    });

    self.resetContractsInConsoleContext(abstractions);
  });
};

Console.prototype.provision = function(callback) {
  var self = this;

  fs.readdir(this.options.contracts_build_directory, function(err, files) {
    if (err) {
      // Error reading the build directory? Must mean it doesn't exist or we don't have access to it.
      // Couldn't provision the contracts if we wanted. It's possible we're hiding very rare FS
      // errors, but that's better than showing the user error messages that will be "build folder
      // doesn't exist" 99.9% of the time.
    }

    var promises = [];
    files = files || [];

    files.forEach(function(file) {
      promises.push(new Promise(function(accept, reject) {
        fs.readFile(path.join(self.options.contracts_build_directory, file), "utf8", function(err, body) {
          if (err) return reject(err);
          try {
            body = JSON.parse(body);
          } catch (e) {
            return reject(new Error("Cannot parse " + file + ": " + e.message));
          }

          accept(body);
        })
      }))
    });

    Promise.all(promises).then(function(json_blobs) {
      var abstractions = json_blobs.map(function(json) {
        var abstraction = contract(json);
        provision(abstraction, self.options);
        return abstraction;
      });

      self.resetContractsInConsoleContext(abstractions);

      callback(null, abstractions);
    }).catch(callback);
  });
};

Console.prototype.resetContractsInConsoleContext = function(abstractions) {
  var self = this;

  abstractions = abstractions || []

  var contextVars = {};

  abstractions.forEach(function(abstraction) {
    contextVars[abstraction.contract_name] = abstraction;
  });

  self.repl.setContextVars(contextVars);
}

Console.prototype.interpret = function(cmd, context, filename, callback) {
  var self = this;

  if (this.command.getCommand(cmd.trim(), this.options.noAliases) != null) {
    return self.command.run(cmd.trim(), this.options, function(err) {
      if (err) {
        // Perform error handling ourselves.
        if (err instanceof TruffleError) {
          console.log(err.message);
        } else {
          // Bubble up all other unexpected errors.
          console.log(err.stack || err.toString());
        }
        return callback();
      }

      // Reprovision after each command as it may change contracts.
      self.provision(function(err, abstractions) {
        // Don't pass abstractions to the callback if they're there or else
        // they'll get printed in the repl.
        callback(err);
      });
    });
  }

  var result;
  try {
    result = vm.runInContext(cmd, context, {
      displayErrors: false
    });
  } catch (e) {
    return callback(e);
  }

  // Resolve all promises. This will leave non-promises alone.
  Promise.resolve(result).then(function(res) { callback(null, res) }).catch(callback);
}

module.exports = Console;
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var path = require('path');
var bluebird = require('bluebird');
var marked = require('marked');
var through = require('through2');

function createLoader(dirname) {
  loadContent.dirname = path.resolve(process.cwd(), dirname || '');

  return loadContent;

  // Returns a Promise to be resolved with an fs.ReadStream of the named file's
  // rendered contents.
  function loadContent(name) {
    var basename = path.join(loadContent.dirname, name);

    // Try each file extension, returning a ReadStream for the first to succeed.
    // The order of the extensions, then, is the order of priority if multiple
    // such files exist.
    return ['html', 'md', 'markdown', 'txt']
      .reduce(function (prev, ext) {
        return prev.catch(function () {
          return new Promise(function (resolve, reject) {
            var stream = fs.createReadStream(basename + '.' + ext)
              .on('error', reject)
              .on('open', function () {
                resolve(stream);
              });
          });
        });
      }, Promise.reject('Failed to read files.'))
      .catch(function () {
        return Promise.reject('No such file found.');
      })
      // To handle parts of the Markdown syntax (such as [][] links) properly
      // requires knowledge of the file beyond the current chunk. Therefore a
      // lot of parsers like Marked require the _entire_ body be know. We
      // maintain a streaming interface to capitalize on improvements in this
      // department down the road, but for now we need to briefly buffer the
      // input stream before parsing for output.
      .then(function (stream) {
        var buffer = [];
        var parser = through(
          function transform(chunk, enc, callback) {
            buffer.push(chunk);

            callback();
          },
          function flush(callback) {
            this.push(marked(buffer.join('')));
            this.push(null);

            callback();
          }
        );

        parser.path = stream.path;

        stream.pipe(parser);

        return parser;
      });
  }
}

module.exports = createLoader;
<commit_msg>Server: Sanitize smart quotes.<commit_after>'use strict';

var fs = require('fs');
var path = require('path');
var bluebird = require('bluebird');
var marked = require('marked');
var through = require('through2');

function createLoader(dirname) {
  loadContent.dirname = path.resolve(process.cwd(), dirname || '');

  return loadContent;

  // Returns a Promise to be resolved with an fs.ReadStream of the named file's
  // rendered contents.
  function loadContent(name) {
    var basename = path.join(loadContent.dirname, name);

    // Try each file extension, returning a ReadStream for the first to succeed.
    // The order of the extensions, then, is the order of priority if multiple
    // such files exist.
    return ['html', 'md', 'markdown', 'txt']
      .reduce(function (prev, ext) {
        return prev.catch(function () {
          return new Promise(function (resolve, reject) {
            var stream = fs.createReadStream(basename + '.' + ext)
              .on('error', reject)
              .on('open', function () {
                resolve(stream);
              });
          });
        });
      }, Promise.reject('Failed to read files.'))
      .catch(function () {
        return Promise.reject('No such file found.');
      })
      // To handle parts of the Markdown syntax (such as [][] links) properly
      // requires knowledge of the file beyond the current chunk. Therefore a
      // lot of parsers like Marked require the _entire_ body be know. We
      // maintain a streaming interface to capitalize on improvements in this
      // department down the road, but for now we need to briefly buffer the
      // input stream before parsing for output.
      .then(function (stream) {
        var buffer = [];
        var parser = through(
          function transform(chunk, enc, callback) {
            buffer.push(chunk);

            callback();
          },
          function flush(callback) {
            // Marked doesn't sanitize "smart quotes", but a few `replace`
            // statements will make quick work of that. Additional sanitization
            // can be added here later, should the need arise.
            this.push(
              marked(buffer.join(''))
                .replace(/‘/g, '‘').replace(/’/g, '’')
                .replace(/“/g, '“').replace(/”/g, '”')
            );
            this.push(null);

            callback();
          }
        );

        parser.path = stream.path;

        stream.pipe(parser);

        return parser;
      });
  }
}

module.exports = createLoader;
<|endoftext|>


<commit_before>/* global $ */

(function (window, document, $) {
    'use strict';

    var LiveEditor = function LiveEditor (params) {
        console.log('Live Editor Init...');
        this.init(params);
        console.log('Live Editor Done...');
    };

    LiveEditor.prototype.init = function (params) {
        var self = this;

        this.initVars(params);
        this.buildIframe(params);
        this.buildTabs();
        this.buildModals(params);
        this.buildPanel(params);
        this.buildToolbar();
        this.initActions();

        this.$editorIframe.on('load', function() {
            self.domOutlineInit();
            self.bindEvents();
            self.$editorIframe.show();
            self.$spinnerContainer.hide();
            self.saveBody();
            self.updateBody();
            self.codePanelUpdate();
            self.dispatchLoadEvent();
            console.log('*** iframe fully loaded! ***');
        });

        this.$editorIframe.addClass('tab-pane__' + this.device);
    };

    LiveEditor.prototype.initVars = function (params) {
        this.device = params.device;
        this.tabsList = params.tabs;
        this.id = params.editor.replace('#', '');
        this.url = params.url;
        this.js = params.js || [];
        this.$editor = $(params.editor);
        this.domOutline = null;
        this.experiments = {};
    };

    LiveEditor.prototype.buildTabs = function () {
        this.tabs = new LiveEditorTabs({
            tabs: this.tabsList,
            parent: this.$header
        });

        this.createExperiments();
    };

    LiveEditor.prototype.createExperiments = function() {
        for(var i=0; i<=this.tabsList.length-1; i++) {
            var name = this.tabs.formatName(this.tabsList[i]);

            if (this.experiments[name] === undefined) {
                this.experiments[name] = {
                    'scriptList': this.js[i] ? [this.js[i]] : [],
                    'undoList': [],
                    'goalList': []
                };
            }
        }
    };

    LiveEditor.prototype.buildIframe = function (params) {
        this.$editorIframe = $('');
        this.$spinnerContainer = $(' Loading...');

        this.$editorIframe.attr({
            'src': this.url,
            'width': '100%',
            'height': '100%',
            'frameborder': '0',
            'allowfullscreen': ''
        });

        this.$header = $('');
        this.$editorIframeContainer = $('
');
        this.$editorIframeContainer.append(this.$spinnerContainer, this.$editorIframe);

        this.$editor.append(this.$header, this.$editorIframeContainer);
        this.$editor.addClass('live-editor');
    };

    LiveEditor.prototype.buildModals = function (params) {
        this.editHtmlModal = new LiveEditorModal({
            editor: this.id,
            data: {
                name: 'edit-html-modal-' + this.id,
                title: 'Edit HTML',
                field: 'textarea',
                hasAceEditor: true
            }
        });

        this.editTextModal = new LiveEditorModal({
            editor: this.id,
            data: {
                name: 'edit-text-modal-' + this.id,
                title: 'Edit Text',
                field: 'textarea'
            }
        });

        new LiveEditorModal({
            editor: this.id,
            data: {
                name: 'edit-classes-modal-' + this.id,
                title: 'Edit Classes',
                field: 'input'
            }
        });

        this.$editHtmlModal = $('#edit-html-modal-' + this.id);
        this.$editTextModal = $('#edit-text-modal-' + this.id);
        this.$editClassesModal = $('#edit-classes-modal-' + this.id);
    };

    LiveEditor.prototype.buildPanel = function () {
        this.codePanel = new LiveEditorCodePanel({
            editorName: this.id,
            appendTo: this.$editor.parent()
        });
    };

    LiveEditor.prototype.buildToolbar = function() {
        this.$modeSelect = $('<select class="form-control" disabled="disabled">').append('<option value="edit">Edit mode</option>')
                                                             .append('<option value="view">View mode</option>');
        this.$buttonAddOption = $('<button class="add-option" type="button">+ add option</button>');
        this.$undoButton = $('<button type="button" class="btn btn-default btn-undo">Undo</button>');
        this.$toolbar = $('
');

        this.$toolbar.append($('
').append(this.$buttonAddOption));
        this.$toolbar.append($('
').append(this.$modeSelect));
        this.$toolbar.append($('
').append(this.$undoButton));

        this.$header.append(this.$toolbar);
    };

    LiveEditor.prototype.currentMode = function() {
        return this.$modeSelect.val();
    };

    LiveEditor.prototype.saveBody = function () {
        this.$iframeBody = this.$editorIframe.contents().find('body');
    };

    LiveEditor.prototype.getIframeBody = function () {
        return this.$editorIframe.contents().find('body');
    };

    LiveEditor.prototype.updateBody = function ($iframeBody) {
        var $body = this.getIframeBody(),
            $iframeBody = $iframeBody || this.$iframeBody,
            mode = this.currentMode();

        this.domOutline.stop();
        this.unselectElements();

        try {
            if (mode == 'edit') {
                $body.replaceWith($iframeBody.clone());
            } else if (mode == 'view') {
                $body.replaceWith($iframeBody);
            }
        }
        catch(err) {
            console.log('Update body error - Mode: ' + mode + ' - error: ' + err);
        }
        finally {
            if (mode == 'edit') {
                this.applyJs();
                this.domOutlineInit();
            }
        }
    };

    LiveEditor.prototype.changeTab = function () {
        this.updateBody();
        this.codePanelUpdate();
        console.log('Change tab!');
    };

    LiveEditor.prototype.initActions = function () {
        this.actions = new LiveEditorActions(this);
    };

    LiveEditor.prototype.applyJs = function (str) {
        // TO DO - test
        var iframeWindow = this.$editorIframe[0].contentWindow,
            scriptList = this.currentExperiment().scriptList;
        // We use replace here to guarantee the jquery been used
        // is from iframe's window.

        if (str !== undefined) {
            eval(str.replace(/\$/g, 'iframeWindow.$'));
        } else {
            for (var i=0, len=scriptList.length; i<=len-1; i++) {
                eval(scriptList[i].replace(/\$/g, 'iframeWindow.$'));
            }
        }
    };

    LiveEditor.prototype.dispatchLoadEvent = function () {
        var _event = new CustomEvent('LiveEditorLoaded', {'detail': {}});
        document.dispatchEvent(_event);
    };

    LiveEditor.prototype.domOutlineInit = function () {
        this.domOutline = new DomOutline({
            realtime: true,
            onClick: this.sendEventOnClick,
            elem: this.$editorIframe.contents().find('html body')
        });
        this.domOutline.start();
    };

    LiveEditor.prototype.modalEvents = function () {
        var self = this;

        // Edit HTML
        this.$editHtmlModal.on('shown.bs.modal', function () {
            var current_without_cache = self.$editorIframe.contents().find(self.currentSelected)[0],
                html = current_without_cache.outerHTML;
            self.editHtmlModal.setValue(html);
        });

        // Edit Text
        this.$editTextModal.on('show.bs.modal', function () {
            var current_without_cache = self.$editorIframe.contents().find(self.currentSelected)[0],
                text = current_without_cache.textContent;
            self.editTextModal.setValue(text);
        });

        // Edit Classes
        this.$editClassesModal.on('show.bs.modal', function () {
            var current_without_cache = self.$editorIframe.contents().find(self.currentSelected),
                classes = current_without_cache.attr('class');
            $(this).find('.modal-body input').val(classes);
        });

        // Rename Modal
        $('#rename-modal').on('show.bs.modal', function () {
            $(this).find('.modal-body input').val('');
        });

        this.bindModalSave(this.$editHtmlModal, 'html');
        this.bindModalSave(this.$editTextModal, 'text');
        this.bindModalSave(this.$editClassesModal, 'classes');
        this.bindModalSave($('#rename-modal'), 'rename-modal');
    };

    LiveEditor.prototype.bindModalSave = function ($modal, label) {
        var self = this,
            saveId = 'edit-' + label + '-save';

        $modal.find('.save-btn').on('click', function() {
            self.operationInit(saveId);
            $modal.modal('hide');
        });
    };

    LiveEditor.prototype.bindEvents = function () {
        var self = this;

        this.modalEvents();

        document.addEventListener('domOutlineOnClick', function () {
            self.setCurrentElement(self.domOutline.element);
            self.openCurrentMenu();
            self.domOutline.pause();

            self.$editorIframe.contents().find('html *').on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                self.unselectElements();
            });

            console.log('dom clicked!');
        }, false);

        document.addEventListener('floatingMenuItemClicked', function (e) {
            // Verify if this logic is already necessary
            if (self.$editor.attr('id') == e.detail.liveEditor) {
                self.operationInit(e.detail.operation);
            }
        }, false);

        this.tabs.$tabs.on('click', 'li', function(e) {
            var op = $(this).data('operation');
            if (op) {
                self.operationInit(op);
                console.log(op);
            }
        });

        this.$editorIframe.contents().keyup(function(e) {
            if (e.keyCode == 27) { // Esc
                self.unselectElements();
            }
        });

        this.$undoButton.off().on('click', function () {
            if (self.currentExperiment().undoList.length) {
                var object = self.currentExperiment().undoList.pop();
                self.currentExperiment().scriptList.pop();
                self.updateBody(object);
                self.codePanelUpdate();
            }
        });

        this.tabs.$tabs.on('shown.bs.tab', 'a[data-toggle="tab"]', function () {
            self.changeTab();
        });

        $('#code-panel').on('show.bs.collapse', function () {
            self.codePanelUpdate();
        });

        this.$modeSelect.on('change', function() {
            self.updateBody();
        });

        this.$buttonAddOption.on('click', function() {
            self.addNewOption();
        });
    };

    LiveEditor.prototype.addNewOption = function () {
        var name = 'Test ' + this.tabsList.length;
        this.tabsList.push(name);
        this.tabs.createTabs([name]);
        this.createExperiments();
        this.updateBody();
    };

    LiveEditor.prototype.unselectElements = function () {
        if (this.$currentSelected && this.currentSelected) {
            this.floatingMenu.close();
            this.domOutline.stop();
            this.domOutline.start();
            this.$currentSelected = null;
            this.currentSelected = null;
            this.$editorIframe.contents().find('html *').off('click');
        }
    };

    LiveEditor.prototype.sendEventOnClick = function (e) {
        var _event = new CustomEvent('domOutlineOnClick', {'detail': e});
        document.dispatchEvent(_event);
    };

    LiveEditor.prototype.setCurrentElement = function ($elem) {
        this.currentSelected = this.getElementPath($elem);
        this.$currentSelected = this.$editorIframe.contents().find(this.currentSelected);
        console.log('Current: ' +  this.currentSelected);
    };

    LiveEditor.prototype.getElementPath = function ($elem) {
        if ($elem.length != 1)
            $elem = $($elem);

        var path, node = $elem;
        while (node.length) {
            var realNode = node[0], name = realNode.localName;
            if (!name) break;
            name = name.toLowerCase();

            var parent = node.parent();

            var siblings = parent.children(name);
            if (siblings.length > 1) {
                name += ':eq(' + siblings.index(realNode) + ')';
            }

            path = name + (path ? '>' + path : '');
            node = parent;
        }

        return path;
    };

    LiveEditor.prototype.getCurrentParentsPath = function () {
        var a = this.$currentSelected[0];
        var els = [];
        while (a) {
            els.unshift(a);
            a = a.parentNode;
        }
        return els;
    };

    LiveEditor.prototype.openCurrentMenu = function () {
        if (this.currentSelected) {
            var $DomOutlineBox = this.$editorIframe.contents().find('.DomOutline_box'),
                top = this.$editorIframe.offset().top,
                left = this.$editorIframe.offset().left,
                scrollTop = this.$editorIframe.contents().scrollTop();

            this.floatingMenu = new FloatingMenu({
                elemId: this.$editor.attr('id'),
                appendTo: this.$editorIframeContainer,
                data: {
                    value: this.$currentSelected.prop('tagName').toLowerCase(),
                    posLeft: {
                        "iframe_left":left,
                        "box_offset_left": $DomOutlineBox.offset().left,
                        "box_width": $DomOutlineBox.width(),
                        "iframe_width": this.$editorIframe.width(),
                    },
                    posTop: {
                        "iframe_top": top,
                        "box_offset_top": $DomOutlineBox.offset().top,
                        "scroll_top": scrollTop,
                        "iframe_height": this.$editorIframe.height(),
                        "box_heigh": $DomOutlineBox.height(),
                    },
                    container: this.containerFormat()
                }
            });

            this.floatingMenu.open();
        } else {
            console.log('No item has been selected...');
        }
    };

    LiveEditor.prototype.containerFormat = function () {
        var pathList = this.getCurrentParentsPath(),
            _list = [];

        for (var i=0; i <= pathList.length - 1; i++) {
            if (pathList[i].tagName) {
                _list.push({
                    value: pathList[i].tagName.toLowerCase(),
                    attrs: {
                        'value': this.getElementPath(pathList[i])
                    }
                });
            }
        }

        return _list;
    };

    LiveEditor.prototype.operationInit = function (operation) {
        if (operation === 'remove') {
            this.actions.currentSelectedRemove();
            this.unselectElements();
        }

        if (operation === 'add-event-click') {
            this.actions.currentSelectedAddEvent('click');
            this.unselectElements();
        }

        if (operation === 'edit-html-save') {
            this.actions.currentSelectedEditHtml();
        }

        if (operation === 'edit-text-save') {
            this.actions.currentSelectedEditText();
        }

        if (operation === 'edit-classes-save') {
            this.actions.currentSelectedEditClasses();
        }

        if (operation === 'edit-rename-modal-save') {
            // TODO - test js
            this.actions.currentOptionRename();
        }

        if (operation === 'delete-option') {
            // TODO - test js
            this.actions.currentOptionDelete();
        }

        if (operation === 'duplicate-option') {
            this.actions.currentOptionDuplicate();
        }

        this.codePanelUpdate();
    };

    LiveEditor.prototype.undoListUpdate = function () {
        this.currentExperiment().undoList.push(this.$iframeBody.clone());
    };

    LiveEditor.prototype.addToScriptList = function (str) {
        var str = str.replace(new RegExp('\t|\n', 'g'), '');
        this.currentExperiment().scriptList.push(str);
    };

    LiveEditor.prototype.codePanelUpdate = function () {
        var str = this.currentExperiment().scriptList.join(' ');
        this.codePanel.aceEditor.aceEditor.setValue(str, -1);
    };

    LiveEditor.prototype.currentExperiment = function () {
        var currentTab = this.tabs.current();
        return this.experiments[currentTab.data('name')];
    };

    window.LiveEditor = LiveEditor;
})(window, document, $);
<commit_msg>Fix rename option<commit_after>/* global $ */

(function (window, document, $) {
    'use strict';

    var LiveEditor = function LiveEditor (params) {
        console.log('Live Editor Init...');
        this.init(params);
        console.log('Live Editor Done...');
    };

    LiveEditor.prototype.init = function (params) {
        var self = this;

        this.initVars(params);
        this.buildIframe(params);
        this.buildTabs();
        this.buildModals(params);
        this.buildPanel(params);
        this.buildToolbar();
        this.initActions();

        this.$editorIframe.on('load', function() {
            self.domOutlineInit();
            self.bindEvents();
            self.$editorIframe.show();
            self.$spinnerContainer.hide();
            self.saveBody();
            self.updateBody();
            self.codePanelUpdate();
            self.dispatchLoadEvent();
            console.log('*** iframe fully loaded! ***');
        });

        this.$editorIframe.addClass('tab-pane__' + this.device);
    };

    LiveEditor.prototype.initVars = function (params) {
        this.device = params.device;
        this.tabsList = params.tabs;
        this.id = params.editor.replace('#', '');
        this.url = params.url;
        this.js = params.js || [];
        this.$editor = $(params.editor);
        this.domOutline = null;
        this.experiments = {};
    };

    LiveEditor.prototype.buildTabs = function () {
        this.tabs = new LiveEditorTabs({
            tabs: this.tabsList,
            parent: this.$header
        });

        this.createExperiments();
    };

    LiveEditor.prototype.createExperiments = function() {
        for(var i=0; i<=this.tabsList.length-1; i++) {
            var name = this.tabs.formatName(this.tabsList[i]);

            if (this.experiments[name] === undefined) {
                this.experiments[name] = {
                    'scriptList': this.js[i] ? [this.js[i]] : [],
                    'undoList': [],
                    'goalList': []
                };
            }
        }
    };

    LiveEditor.prototype.buildIframe = function (params) {
        this.$editorIframe = $('');
        this.$spinnerContainer = $(' Loading...');

        this.$editorIframe.attr({
            'src': this.url,
            'width': '100%',
            'height': '100%',
            'frameborder': '0',
            'allowfullscreen': ''
        });

        this.$header = $('');
        this.$editorIframeContainer = $('
');
        this.$editorIframeContainer.append(this.$spinnerContainer, this.$editorIframe);

        this.$editor.append(this.$header, this.$editorIframeContainer);
        this.$editor.addClass('live-editor');
    };

    LiveEditor.prototype.buildModals = function (params) {
        this.editHtmlModal = new LiveEditorModal({
            editor: this.id,
            data: {
                name: 'edit-html-modal-' + this.id,
                title: 'Edit HTML',
                field: 'textarea',
                hasAceEditor: true
            }
        });

        this.editTextModal = new LiveEditorModal({
            editor: this.id,
            data: {
                name: 'edit-text-modal-' + this.id,
                title: 'Edit Text',
                field: 'textarea'
            }
        });

        new LiveEditorModal({
            editor: this.id,
            data: {
                name: 'edit-classes-modal-' + this.id,
                title: 'Edit Classes',
                field: 'input'
            }
        });

        this.$editHtmlModal = $('#edit-html-modal-' + this.id);
        this.$editTextModal = $('#edit-text-modal-' + this.id);
        this.$editClassesModal = $('#edit-classes-modal-' + this.id);
    };

    LiveEditor.prototype.buildPanel = function () {
        this.codePanel = new LiveEditorCodePanel({
            editorName: this.id,
            appendTo: this.$editor.parent()
        });
    };

    LiveEditor.prototype.buildToolbar = function() {
        this.$modeSelect = $('<select class="form-control" disabled="disabled">').append('<option value="edit">Edit mode</option>')
                                                             .append('<option value="view">View mode</option>');
        this.$buttonAddOption = $('<button class="add-option" type="button">+ add option</button>');
        this.$undoButton = $('<button type="button" class="btn btn-default btn-undo">Undo</button>');
        this.$toolbar = $('
');

        this.$toolbar.append($('
').append(this.$buttonAddOption));
        this.$toolbar.append($('
').append(this.$modeSelect));
        this.$toolbar.append($('
').append(this.$undoButton));

        this.$header.append(this.$toolbar);
    };

    LiveEditor.prototype.currentMode = function() {
        return this.$modeSelect.val();
    };

    LiveEditor.prototype.saveBody = function () {
        this.$iframeBody = this.$editorIframe.contents().find('body');
    };

    LiveEditor.prototype.getIframeBody = function () {
        return this.$editorIframe.contents().find('body');
    };

    LiveEditor.prototype.updateBody = function ($iframeBody) {
        var $body = this.getIframeBody(),
            $iframeBody = $iframeBody || this.$iframeBody,
            mode = this.currentMode();

        this.domOutline.stop();
        this.unselectElements();

        try {
            if (mode == 'edit') {
                $body.replaceWith($iframeBody.clone());
            } else if (mode == 'view') {
                $body.replaceWith($iframeBody);
            }
        }
        catch(err) {
            console.log('Update body error - Mode: ' + mode + ' - error: ' + err);
        }
        finally {
            if (mode == 'edit') {
                this.applyJs();
                this.domOutlineInit();
            }
        }
    };

    LiveEditor.prototype.changeTab = function () {
        this.updateBody();
        this.codePanelUpdate();
        console.log('Change tab!');
    };

    LiveEditor.prototype.initActions = function () {
        this.actions = new LiveEditorActions(this);
    };

    LiveEditor.prototype.applyJs = function (str) {
        // TO DO - test
        var iframeWindow = this.$editorIframe[0].contentWindow,
            scriptList = this.currentExperiment().scriptList;
        // We use replace here to guarantee the jquery been used
        // is from iframe's window.

        if (str !== undefined) {
            eval(str.replace(/\$/g, 'iframeWindow.$'));
        } else {
            for (var i=0, len=scriptList.length; i<=len-1; i++) {
                eval(scriptList[i].replace(/\$/g, 'iframeWindow.$'));
            }
        }
    };

    LiveEditor.prototype.dispatchLoadEvent = function () {
        var _event = new CustomEvent('LiveEditorLoaded', {'detail': {}});
        document.dispatchEvent(_event);
    };

    LiveEditor.prototype.domOutlineInit = function () {
        this.domOutline = new DomOutline({
            realtime: true,
            onClick: this.sendEventOnClick,
            elem: this.$editorIframe.contents().find('html body')
        });
        this.domOutline.start();
    };

    LiveEditor.prototype.modalEvents = function () {
        var self = this;

        // Edit HTML
        this.$editHtmlModal.on('shown.bs.modal', function () {
            var current_without_cache = self.$editorIframe.contents().find(self.currentSelected)[0],
                html = current_without_cache.outerHTML;
            self.editHtmlModal.setValue(html);
        });

        // Edit Text
        this.$editTextModal.on('show.bs.modal', function () {
            var current_without_cache = self.$editorIframe.contents().find(self.currentSelected)[0],
                text = current_without_cache.textContent;
            self.editTextModal.setValue(text);
        });

        // Edit Classes
        this.$editClassesModal.on('show.bs.modal', function () {
            var current_without_cache = self.$editorIframe.contents().find(self.currentSelected),
                classes = current_without_cache.attr('class');
            $(this).find('.modal-body input').val(classes);
        });

        // Rename Modal
        $('#rename-modal').on('show.bs.modal', function () {
            $(this).find('.modal-body input').val('');
        });

        this.bindModalSave(this.$editHtmlModal, 'html');
        this.bindModalSave(this.$editTextModal, 'text');
        this.bindModalSave(this.$editClassesModal, 'classes');
        this.bindModalSave($('#rename-modal'), 'rename-modal');
    };

    LiveEditor.prototype.bindModalSave = function ($modal, label) {
        var self = this,
            saveId = 'edit-' + label + '-save';

        $modal.find('.save-btn').on('click', function() {
            self.operationInit(saveId);
            $modal.modal('hide');
        });
    };

    LiveEditor.prototype.bindEvents = function () {
        var self = this;

        this.modalEvents();

        document.addEventListener('domOutlineOnClick', function () {
            self.setCurrentElement(self.domOutline.element);
            self.openCurrentMenu();
            self.domOutline.pause();

            self.$editorIframe.contents().find('html *').on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                self.unselectElements();
            });

            console.log('dom clicked!');
        }, false);

        document.addEventListener('floatingMenuItemClicked', function (e) {
            // Verify if this logic is already necessary
            if (self.$editor.attr('id') == e.detail.liveEditor) {
                self.operationInit(e.detail.operation);
            }
        }, false);

        this.tabs.$tabs.on('click', 'li', function(e) {
            var op = $(this).data('operation');
            if (op) {
                self.operationInit(op);
                console.log(op);
            }
        });

        this.$editorIframe.contents().keyup(function(e) {
            if (e.keyCode == 27) { // Esc
                self.unselectElements();
            }
        });

        this.$undoButton.off().on('click', function () {
            if (self.currentExperiment().undoList.length) {
                var object = self.currentExperiment().undoList.pop();
                self.currentExperiment().scriptList.pop();
                self.updateBody(object);
                self.codePanelUpdate();
            }
        });

        this.tabs.$tabs.on('shown.bs.tab', 'a[data-toggle="tab"]', function () {
            self.changeTab();
        });

        $('#code-panel').on('show.bs.collapse', function () {
            self.codePanelUpdate();
        });

        this.$modeSelect.on('change', function() {
            self.updateBody();
        });

        this.$buttonAddOption.on('click', function() {
            self.addNewOption();
        });
    };

    LiveEditor.prototype.addNewOption = function () {
        var name = 'Test ' + this.tabsList.length;
        this.tabsList.push(name);
        this.tabs.createTabs([name]);
        this.createExperiments();
        this.updateBody();
    };

    LiveEditor.prototype.unselectElements = function () {
        if (this.$currentSelected && this.currentSelected) {
            this.floatingMenu.close();
            this.domOutline.stop();
            this.domOutline.start();
            this.$currentSelected = null;
            this.currentSelected = null;
            this.$editorIframe.contents().find('html *').off('click');
        }
    };

    LiveEditor.prototype.sendEventOnClick = function (e) {
        var _event = new CustomEvent('domOutlineOnClick', {'detail': e});
        document.dispatchEvent(_event);
    };

    LiveEditor.prototype.setCurrentElement = function ($elem) {
        this.currentSelected = this.getElementPath($elem);
        this.$currentSelected = this.$editorIframe.contents().find(this.currentSelected);
        console.log('Current: ' +  this.currentSelected);
    };

    LiveEditor.prototype.getElementPath = function ($elem) {
        if ($elem.length != 1)
            $elem = $($elem);

        var path, node = $elem;
        while (node.length) {
            var realNode = node[0], name = realNode.localName;
            if (!name) break;
            name = name.toLowerCase();

            var parent = node.parent();

            var siblings = parent.children(name);
            if (siblings.length > 1) {
                name += ':eq(' + siblings.index(realNode) + ')';
            }

            path = name + (path ? '>' + path : '');
            node = parent;
        }

        return path;
    };

    LiveEditor.prototype.getCurrentParentsPath = function () {
        var a = this.$currentSelected[0];
        var els = [];
        while (a) {
            els.unshift(a);
            a = a.parentNode;
        }
        return els;
    };

    LiveEditor.prototype.openCurrentMenu = function () {
        if (this.currentSelected) {
            var $DomOutlineBox = this.$editorIframe.contents().find('.DomOutline_box'),
                top = this.$editorIframe.offset().top,
                left = this.$editorIframe.offset().left,
                scrollTop = this.$editorIframe.contents().scrollTop();

            this.floatingMenu = new FloatingMenu({
                elemId: this.$editor.attr('id'),
                appendTo: this.$editorIframeContainer,
                data: {
                    value: this.$currentSelected.prop('tagName').toLowerCase(),
                    posLeft: {
                        "iframe_left":left,
                        "box_offset_left": $DomOutlineBox.offset().left,
                        "box_width": $DomOutlineBox.width(),
                        "iframe_width": this.$editorIframe.width(),
                    },
                    posTop: {
                        "iframe_top": top,
                        "box_offset_top": $DomOutlineBox.offset().top,
                        "scroll_top": scrollTop,
                        "iframe_height": this.$editorIframe.height(),
                        "box_heigh": $DomOutlineBox.height(),
                    },
                    container: this.containerFormat()
                }
            });

            this.floatingMenu.open();
        } else {
            console.log('No item has been selected...');
        }
    };

    LiveEditor.prototype.containerFormat = function () {
        var pathList = this.getCurrentParentsPath(),
            _list = [];

        for (var i=0; i <= pathList.length - 1; i++) {
            if (pathList[i].tagName) {
                _list.push({
                    value: pathList[i].tagName.toLowerCase(),
                    attrs: {
                        'value': this.getElementPath(pathList[i])
                    }
                });
            }
        }

        return _list;
    };

    LiveEditor.prototype.operationInit = function (operation) {
        if (operation === 'remove') {
            this.actions.currentSelectedRemove();
            this.unselectElements();
        }

        if (operation === 'add-event-click') {
            this.actions.currentSelectedAddEvent('click');
            this.unselectElements();
        }

        if (operation === 'edit-html-save') {
            this.actions.currentSelectedEditHtml();
        }

        if (operation === 'edit-text-save') {
            this.actions.currentSelectedEditText();
        }

        if (operation === 'edit-classes-save') {
            this.actions.currentSelectedEditClasses();
        }

        if (operation === 'edit-rename-modal-save') {
            // TODO - test js
            this.actions.currentOptionRename();
        }

        if (operation === 'delete-option') {
            // TODO - test js
            this.actions.currentOptionDelete();
        }

        if (operation === 'duplicate-option') {
            this.actions.currentOptionDuplicate();
        }

        this.codePanelUpdate();
    };

    LiveEditor.prototype.undoListUpdate = function () {
        this.currentExperiment().undoList.push(this.$iframeBody.clone());
    };

    LiveEditor.prototype.addToScriptList = function (str) {
        var str = str.replace(new RegExp('\t|\n', 'g'), '');
        this.currentExperiment().scriptList.push(str);
    };

    LiveEditor.prototype.codePanelUpdate = function () {
        var str = this.currentExperiment().scriptList.join(' ');
        this.codePanel.aceEditor.aceEditor.setValue(str, -1);
    };

    LiveEditor.prototype.currentExperiment = function () {
        var currentTab = this.tabs.current();
        return this.experiments[currentTab.attr('data-name')];
    };

    window.LiveEditor = LiveEditor;
})(window, document, $);
<|endoftext|>