<commit_before>/** @module trybuynumber */

// Allow for later instantiation despite singleton
TryBuyNumber.prototype.TryBuyNumber = TryBuyNumber;
module.exports = exports = new TryBuyNumber();

var fs = require('fs'),
    path = require('path'),
    phone = require('node-phonenumber'),
    e164 = require('e164'),
    when = require('when'),
    winston = require('winston'),
    twilio = require('twilio');

var logger = new(winston.Logger)({
    transports: [
        new(winston.transports.Console)({
            level: 'warn'
        }),
        new(winston.transports.File)({
            filename: 'TryBuyNumber.log',
            level: 'info'
        })
    ]
});

// Squelch console output when used as a module
if (require.main != module) {
    logger.transports.console.silent = true;
}

var phoneUtil = phone.PhoneNumberUtil.getInstance();

var area_codes;
// Need these before we begin
require('csv').parse(fs.readFileSync(__dirname + '/area-codes.csv').toString(), {
    columns: ['area_code', 'state'],
    objname: 'area_code'
}, function(err, output) {
    area_codes = output;
});

/**
 * @class
 * Interacts with the Twilio API to acquire a specified US phone number (E.164)
 * or a suitable alternative should the desired number not be available.
 * Engages in lazy initialization if a config is provided.
 * @param {Object} config - Will be passed to {@link init} before calls are made.
 */
function TryBuyNumber(config) {
    if (!(this instanceof TryBuyNumber)) {
        return new TryBuyNumber(config);
    }

    this.config = config;
    if((typeof this.config) === 'string'){
        this.config = path.resolve(this.config);
    }
    this.isInitialized = false;  // We'll initialize later
}

/**
 * Initializes the TryBuyNumber instance for use.
 * @param {Object|string} config Either a config object or path to a config
 * json file with the data structure below.
 *

 *  {
 *      "creds": {
 *          "query": {
 *              "sid": "<my_query_sid>",
 *              "token": "<my_query_token>"
 *          },
 *          "buy": {
 *              "sid": "<my_buy_sid>",
 *              "token": "<my_buy_token>"
 *          }
 *      }
 *  }

 * @param {Object} config.creds - Configurable Twilio credentials
 * @param {Object} config.creds.query - Credentials for check available phone number requests
 * @param {string} config.creds.query.sid - Twilio Sid
 * @param {string} config.creds.query.token - Twilio Auth Token
 * @param {Object} config.creds.buy - Credentials for purchase phone number requests
 * @param {string} config.creds.buy.sid - Twilio Sid
 * @param {string} config.creds.buy.token - Twilio Auth Token
 */
TryBuyNumber.prototype.init = function(config){
    if ((typeof config) === 'string') {
        this.config = require(path.resolve(config));
        config = this.config;
    } else {
        this.config        = config = config    || {};
        config.creds       = config.creds       || {};
        config.creds.query = config.creds.query || {};
        config.creds.buy   = config.creds.buy   || {};
    }

    logger.info("TryBuyNumber created with config");
    logger.info(config);

    // If these credentials aren't provided, Twilio API will default to env vars:
    // TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN
    this.queryClient = new twilio.RestClient(config.creds.query.sid, config.creds.query.token);
    this.buyClient   = new twilio.RestClient(config.creds.buy.sid,   config.creds.buy.token);
    this.isInitialized = true;
};

/**
 * Helps perform the lazy init.
 * @private
 */
TryBuyNumber.prototype._ensureInit = function(){
    if(!this.isInitialized){
        this.init(this.config);
    }
};

/**
 * Ensures that constraints object is acceptable.
 * @param {Object} constraints - Information about the number
 * @param {string} constraints.nearPhoneNumber - The number to check
 * @returns {Promise} - Fulfills to
 *

 *      {
 *          number: <normalized_phone_number>,
 *          code: <country_code>
 *      }

 *      if succcess; rejects with Error if constraints are unacceptable.
 */
TryBuyNumber.prototype.validateConstraints = function(constraints) {
    this._ensureInit();

    var err;

    // Fast fail for falsy input
    if (!constraints || !constraints.nearPhoneNumber) {
        return when.reject(new Error("No number provided"));
    }

    // Normalize the input for further processing
    var normalized;
    try {
        normalized = phoneUtil.parse(constraints.nearPhoneNumber);
    } catch (ex) {
        err = "Provided number was invalid: " + constraints.nearPhoneNumber;
        logger.error(err);
        return when.reject(new Error(err));
    }

    // Make sure the number resolves to a country
    var numberE164 = phoneUtil.format(normalized, phone.PhoneNumberFormat.E164)
    var country = e164.lookup(numberE164.substring(1)); //e164 doesn't like '+'
    if (!country) {
        err = "No country code for number: " + constraints.nearPhoneNumber;
        logger.error(err);
        return when.reject(new Error(err));
    }

    // Enforce the US assumption
    if (country.code !== 'US') {
        err = "Configured to only handle US numbers; number: " + constraints.nearPhoneNumber + ", code: " + country.code;
        logger.error(err);
        return when.reject(new Error(err));
    }
    var area_code = phoneUtil.format(normalized).split('-')[0];

    return when({
        number: numberE164,
        code: country.code,
        area_code: area_code,
        state: area_codes[area_code].state
    });
};

/**
 * Uses the Twilio helper library to check a phone number.
 * @param {Object} constraints - Information about the number
 * @param {string} constraints.nearPhoneNumber - The number to check
 * @returns {Promise} - Fulfills to an available number {string} if successful;
 * rejects with Error otherwise.
 */
TryBuyNumber.prototype.queryPhoneNumberAsync = function(constraints) {
    this._ensureInit();

    var self = this;
    return this.validateConstraints(constraints)
        .then(function(numberAndCode) {
            //logger.info(numberAndCode);
            logger.info("Looking for  phone number similar to " + numberAndCode.number);
            return maybeUseAreaCode(self.queryClient, numberAndCode.code, numberAndCode.area_code)
                .then(maybeUseState.bind(null, self.queryClient, numberAndCode.code, numberAndCode.state))
                .then(maybeExtractNumber)
        })
        .catch(function(e) {
            err = "Error querying Twilio: " + e.message;
            logger.error(err);
            return when.reject(new Error(err, e));
        });
};

/**
 * Uses the Twilio helper library to purchase a phone number.
 * @param {Object} constraints - Information about the number
 * @param {string} constraints.nearPhoneNumber - The number to buy if available
 * @returns {Promise} Fulfills to purchased number {string} if successful;
 * rejects with Error otherwise.
 */
TryBuyNumber.prototype.purchasePhoneNumberAsync = function(constraints, bypassQuery) {
    this._ensureInit();

    var self = this;
    if(bypassQuery){
        return self.buyClient.incomingPhoneNumbers.create({
            phoneNumber: constraints.nearPhoneNumber
        })
        .then(function(number){
            logger.info(number);
            return when(number.phone_number);
        })
        .catch(function(e){
            err = new Error(e.message);
            logger.error(e.message);
            return when.reject(err);
        });
    } else {
        return this.queryPhoneNumberAsync(constraints)
            .then(function(number) {
                // There's a race condition here. If the number we want
                // gets snatched up before buying, then this next call
                // will fail.
                return self.buyClient.incomingPhoneNumbers.create({
                    phoneNumber: number
                })
                .then(function(num){
                    return when(num.phone_number);
                });
            })
            .catch(function(e) {
                err = new Error(e.message);
                logger.error(e.message);
                return when.reject(err);
            });
    }
};

function maybeUseAreaCode(client, country, area_code) {
    if (!client) {
        err = "maybeUseAreaCode requires 'client' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else if (!country) {
        err = "maybeUseAreaCode requires 'country' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else {
        if (area_code) {
            logger.info("Trying area code " + area_code);
            //logger.info("Number not available: " + numberAndCode.number);
            // Desired number was not available; try for same area code
            return client.availablePhoneNumbers(country).local.get({
                areaCode: area_code,
                excludeAllAddressRequired: "false",
                excludeLocalAddressRequired: "false",
                excludeForeignAddressRequired: "false"
            });
        } else {
            err = "maybeUseAreaCode requires 'area_code' argument";
            logger.error(err);
            return when.reject(new Error(err));
        }
    }
}

function maybeUseState(client, country, state, data) {
    if (data && data.available_phone_numbers && data.available_phone_numbers.length > 0) {
        logger.info("Skipping state");
        //logger.info("Suitable number found for area code " + area_code + "; " + data.available_phone_numbers[0].phone_number);
        return when(data); //.available_phone_numbers[0].phone_number);
    } else if (!client) {
        err = "maybeUseState requires 'client' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else if (!country) {
        err = "maybeUseState requires 'country' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else {
        //logger.info("No numbers available for area code " + area_code);
        logger.info("Trying state " + state);
        if (state) {
            return client.availablePhoneNumbers(country).local.get({
                inRegion: state,
                excludeAllAddressRequired: "false",
                excludeLocalAddressRequired: "false",
                excludeForeignAddressRequired: "false"
            });
        } else {
            err = "maybeUseState requires 'state' argument";
            logger.error(err);
            return when.reject(new Error(err));
        }
    }
}

function maybeExtractNumber(data) {
    logger.info(data);
    if (data && data.available_phone_numbers && data.available_phone_numbers.length > 0) {
        logger.info("Suitable number found: " + data.available_phone_numbers[0].phone_number);
        return when(data.available_phone_numbers[0].phone_number);
    } else {
        err = "No suitable number available";
        logger.error(err);
        return when.reject(new Error(err));
    }
}

<commit_msg>Update docstring<commit_after>/** @module trybuynumber */

// Allow for later instantiation despite singleton
TryBuyNumber.prototype.TryBuyNumber = TryBuyNumber;
module.exports = exports = new TryBuyNumber();

var fs = require('fs'),
    path = require('path'),
    phone = require('node-phonenumber'),
    e164 = require('e164'),
    when = require('when'),
    winston = require('winston'),
    twilio = require('twilio');

var logger = new(winston.Logger)({
    transports: [
        new(winston.transports.Console)({
            level: 'warn'
        }),
        new(winston.transports.File)({
            filename: 'TryBuyNumber.log',
            level: 'info'
        })
    ]
});

// Squelch console output when used as a module
if (require.main != module) {
    logger.transports.console.silent = true;
}

var phoneUtil = phone.PhoneNumberUtil.getInstance();

var area_codes;
// Need these before we begin
require('csv').parse(fs.readFileSync(__dirname + '/area-codes.csv').toString(), {
    columns: ['area_code', 'state'],
    objname: 'area_code'
}, function(err, output) {
    area_codes = output;
});

/**
 * @class
 * Interacts with the Twilio API to acquire a US phone number (E.164)
 * similar to one provided.
 * Engages in lazy initialization if a config is provided.
 * @param {Object} config - Will be passed to {@link init} before calls are made.
 */
function TryBuyNumber(config) {
    if (!(this instanceof TryBuyNumber)) {
        return new TryBuyNumber(config);
    }

    this.config = config;
    if((typeof this.config) === 'string'){
        this.config = path.resolve(this.config);
    }
    this.isInitialized = false;  // We'll initialize later
}

/**
 * Initializes the TryBuyNumber instance for use.
 * @param {Object|string} config Either a config object or path to a config
 * json file with the data structure below.
 *

 *  {
 *      "creds": {
 *          "query": {
 *              "sid": "<my_query_sid>",
 *              "token": "<my_query_token>"
 *          },
 *          "buy": {
 *              "sid": "<my_buy_sid>",
 *              "token": "<my_buy_token>"
 *          }
 *      }
 *  }

 * @param {Object} config.creds - Configurable Twilio credentials
 * @param {Object} config.creds.query - Credentials for check available phone number requests
 * @param {string} config.creds.query.sid - Twilio Sid
 * @param {string} config.creds.query.token - Twilio Auth Token
 * @param {Object} config.creds.buy - Credentials for purchase phone number requests
 * @param {string} config.creds.buy.sid - Twilio Sid
 * @param {string} config.creds.buy.token - Twilio Auth Token
 */
TryBuyNumber.prototype.init = function(config){
    if ((typeof config) === 'string') {
        this.config = require(path.resolve(config));
        config = this.config;
    } else {
        this.config        = config = config    || {};
        config.creds       = config.creds       || {};
        config.creds.query = config.creds.query || {};
        config.creds.buy   = config.creds.buy   || {};
    }

    logger.info("TryBuyNumber created with config");
    logger.info(config);

    // If these credentials aren't provided, Twilio API will default to env vars:
    // TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN
    this.queryClient = new twilio.RestClient(config.creds.query.sid, config.creds.query.token);
    this.buyClient   = new twilio.RestClient(config.creds.buy.sid,   config.creds.buy.token);
    this.isInitialized = true;
};

/**
 * Helps perform the lazy init.
 * @private
 */
TryBuyNumber.prototype._ensureInit = function(){
    if(!this.isInitialized){
        this.init(this.config);
    }
};

/**
 * Ensures that constraints object is acceptable.
 * @param {Object} constraints - Information about the number
 * @param {string} constraints.nearPhoneNumber - The number to check
 * @returns {Promise} - Fulfills to
 *

 *      {
 *          number: <normalized_phone_number>,
 *          code: <country_code>
 *      }

 *      if succcess; rejects with Error if constraints are unacceptable.
 */
TryBuyNumber.prototype.validateConstraints = function(constraints) {
    this._ensureInit();

    var err;

    // Fast fail for falsy input
    if (!constraints || !constraints.nearPhoneNumber) {
        return when.reject(new Error("No number provided"));
    }

    // Normalize the input for further processing
    var normalized;
    try {
        normalized = phoneUtil.parse(constraints.nearPhoneNumber);
    } catch (ex) {
        err = "Provided number was invalid: " + constraints.nearPhoneNumber;
        logger.error(err);
        return when.reject(new Error(err));
    }

    // Make sure the number resolves to a country
    var numberE164 = phoneUtil.format(normalized, phone.PhoneNumberFormat.E164)
    var country = e164.lookup(numberE164.substring(1)); //e164 doesn't like '+'
    if (!country) {
        err = "No country code for number: " + constraints.nearPhoneNumber;
        logger.error(err);
        return when.reject(new Error(err));
    }

    // Enforce the US assumption
    if (country.code !== 'US') {
        err = "Configured to only handle US numbers; number: " + constraints.nearPhoneNumber + ", code: " + country.code;
        logger.error(err);
        return when.reject(new Error(err));
    }
    var area_code = phoneUtil.format(normalized).split('-')[0];

    return when({
        number: numberE164,
        code: country.code,
        area_code: area_code,
        state: area_codes[area_code].state
    });
};

/**
 * Uses the Twilio helper library to check a phone number.
 * @param {Object} constraints - Information about the number
 * @param {string} constraints.nearPhoneNumber - The number to check
 * @returns {Promise} - Fulfills to an available number {string} if successful;
 * rejects with Error otherwise.
 */
TryBuyNumber.prototype.queryPhoneNumberAsync = function(constraints) {
    this._ensureInit();

    var self = this;
    return this.validateConstraints(constraints)
        .then(function(numberAndCode) {
            //logger.info(numberAndCode);
            logger.info("Looking for  phone number similar to " + numberAndCode.number);
            return maybeUseAreaCode(self.queryClient, numberAndCode.code, numberAndCode.area_code)
                .then(maybeUseState.bind(null, self.queryClient, numberAndCode.code, numberAndCode.state))
                .then(maybeExtractNumber)
        })
        .catch(function(e) {
            err = "Error querying Twilio: " + e.message;
            logger.error(err);
            return when.reject(new Error(err, e));
        });
};

/**
 * Uses the Twilio helper library to purchase a phone number.
 * @param {Object} constraints - Information about the number
 * @param {string} constraints.nearPhoneNumber - The number to buy if available
 * @returns {Promise} Fulfills to purchased number {string} if successful;
 * rejects with Error otherwise.
 */
TryBuyNumber.prototype.purchasePhoneNumberAsync = function(constraints, bypassQuery) {
    this._ensureInit();

    var self = this;
    if(bypassQuery){
        return self.buyClient.incomingPhoneNumbers.create({
            phoneNumber: constraints.nearPhoneNumber
        })
        .then(function(number){
            logger.info(number);
            return when(number.phone_number);
        })
        .catch(function(e){
            err = new Error(e.message);
            logger.error(e.message);
            return when.reject(err);
        });
    } else {
        return this.queryPhoneNumberAsync(constraints)
            .then(function(number) {
                // There's a race condition here. If the number we want
                // gets snatched up before buying, then this next call
                // will fail.
                return self.buyClient.incomingPhoneNumbers.create({
                    phoneNumber: number
                })
                .then(function(num){
                    return when(num.phone_number);
                });
            })
            .catch(function(e) {
                err = new Error(e.message);
                logger.error(e.message);
                return when.reject(err);
            });
    }
};

function maybeUseAreaCode(client, country, area_code) {
    if (!client) {
        err = "maybeUseAreaCode requires 'client' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else if (!country) {
        err = "maybeUseAreaCode requires 'country' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else {
        if (area_code) {
            logger.info("Trying area code " + area_code);
            //logger.info("Number not available: " + numberAndCode.number);
            // Desired number was not available; try for same area code
            return client.availablePhoneNumbers(country).local.get({
                areaCode: area_code,
                excludeAllAddressRequired: "false",
                excludeLocalAddressRequired: "false",
                excludeForeignAddressRequired: "false"
            });
        } else {
            err = "maybeUseAreaCode requires 'area_code' argument";
            logger.error(err);
            return when.reject(new Error(err));
        }
    }
}

function maybeUseState(client, country, state, data) {
    if (data && data.available_phone_numbers && data.available_phone_numbers.length > 0) {
        logger.info("Skipping state");
        //logger.info("Suitable number found for area code " + area_code + "; " + data.available_phone_numbers[0].phone_number);
        return when(data); //.available_phone_numbers[0].phone_number);
    } else if (!client) {
        err = "maybeUseState requires 'client' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else if (!country) {
        err = "maybeUseState requires 'country' argument";
        logger.error(err);
        return when.reject(new Error(err));
    } else {
        //logger.info("No numbers available for area code " + area_code);
        logger.info("Trying state " + state);
        if (state) {
            return client.availablePhoneNumbers(country).local.get({
                inRegion: state,
                excludeAllAddressRequired: "false",
                excludeLocalAddressRequired: "false",
                excludeForeignAddressRequired: "false"
            });
        } else {
            err = "maybeUseState requires 'state' argument";
            logger.error(err);
            return when.reject(new Error(err));
        }
    }
}

function maybeExtractNumber(data) {
    logger.info(data);
    if (data && data.available_phone_numbers && data.available_phone_numbers.length > 0) {
        logger.info("Suitable number found: " + data.available_phone_numbers[0].phone_number);
        return when(data.available_phone_numbers[0].phone_number);
    } else {
        err = "No suitable number available";
        logger.error(err);
        return when.reject(new Error(err));
    }
}

<|endoftext|>


<commit_before>'use strict'

const should = require('should')
const {assign, repeat} = require('lodash')

const schema = require('../../core/schema')

const baseFixture = {
  title: 'Vendo Mistral Syncro 92l 2007 - 280€',
  price: 280,
  category: 'particular',
  seller: 'particular',
  provider: 'totalwind',
  link: 'http://totalwind.net/foro/viewtopic.php?f=48&t=102239',
  createdAt: '1466590680000',
  updatedAt: '1467970920000'
}

describe('schema » validation', function () {
  describe('calculated properties', function () {
    describe('condition', function () {
      describe('based on seller', function () {
        it('particular → used', function (done) {
          const fixture = assign({}, baseFixture, {seller: 'particular'})
          schema(fixture, function (err, doc) {
            should(doc.condition).be.equal('used')
            done(err)
          })
        })

        it('used → used', function (done) {
          const fixture = assign({}, baseFixture, {seller: 'used'})
          schema(fixture, function (err, doc) {
            should(doc.condition).be.equal('used')
            done(err)
          })
        })

        it('otherwise, new', function (done) {
          const fixture = assign({}, baseFixture, {seller: 'offer'})
          schema(fixture, function (err, doc) {
            should(doc.condition).be.equal('new')
            done(err)
          })
        })
      })
    })

    describe('referral link', function () {
      it('referral keyword is present', function (done) {
        const fixture = assign({}, baseFixture, {
          link: 'http://totalwind.net/foro/viewtopic.php?f=48&t=102239'
        })
        const expected = 'http://totalwind.net/foro/viewtopic.php?f=48&t=102239&ref=windtodayco'

        schema(fixture, function (err, doc) {
          should(doc.link).be.equal(expected)
          done(err)
        })
      })
    })
  })

  describe('static properties', function () {
    describe('behavior', function () {
      it("optional fields don't throw validation errors", function (done) {
        schema(baseFixture, done)
      })
      it('required fields need to be present', function (done) {
        schema({}, function (err) {
          should.exist(err)
          done()
        })
      })
    })

    describe.only('title', function () {
      it('throw error if not reached mininum length', function (done) {
        const fixture = assign({}, baseFixture, {title: 'Vendo velas windsurf'})
        schema(fixture, function (err, doc) {
          should(err).be.an.Error()
          done()
        })
      })

      it('throw error if reached maximum length', function (done) {
        const title = repeat('n', 141)
        const fixture = assign({}, baseFixture, {title})
        schema(fixture, function (err, doc) {
          should(err).be.an.Error()
          done()
        })
      })
    })
  })
})
<commit_msg>Remove describe.only<commit_after>'use strict'

const should = require('should')
const {assign, repeat} = require('lodash')

const schema = require('../../core/schema')

const baseFixture = {
  title: 'Vendo Mistral Syncro 92l 2007 - 280€',
  price: 280,
  category: 'particular',
  seller: 'particular',
  provider: 'totalwind',
  link: 'http://totalwind.net/foro/viewtopic.php?f=48&t=102239',
  createdAt: '1466590680000',
  updatedAt: '1467970920000'
}

describe('schema » validation', function () {
  describe('calculated properties', function () {
    describe('condition', function () {
      describe('based on seller', function () {
        it('particular → used', function (done) {
          const fixture = assign({}, baseFixture, {seller: 'particular'})
          schema(fixture, function (err, doc) {
            should(doc.condition).be.equal('used')
            done(err)
          })
        })

        it('used → used', function (done) {
          const fixture = assign({}, baseFixture, {seller: 'used'})
          schema(fixture, function (err, doc) {
            should(doc.condition).be.equal('used')
            done(err)
          })
        })

        it('otherwise, new', function (done) {
          const fixture = assign({}, baseFixture, {seller: 'offer'})
          schema(fixture, function (err, doc) {
            should(doc.condition).be.equal('new')
            done(err)
          })
        })
      })
    })

    describe('referral link', function () {
      it('referral keyword is present', function (done) {
        const fixture = assign({}, baseFixture, {
          link: 'http://totalwind.net/foro/viewtopic.php?f=48&t=102239'
        })
        const expected = 'http://totalwind.net/foro/viewtopic.php?f=48&t=102239&ref=windtodayco'

        schema(fixture, function (err, doc) {
          should(doc.link).be.equal(expected)
          done(err)
        })
      })
    })
  })

  describe('static properties', function () {
    describe('behavior', function () {
      it("optional fields don't throw validation errors", function (done) {
        schema(baseFixture, done)
      })
      it('required fields need to be present', function (done) {
        schema({}, function (err) {
          should.exist(err)
          done()
        })
      })
    })

    describe('title', function () {
      it('throw error if not reached mininum length', function (done) {
        const fixture = assign({}, baseFixture, {title: 'Vendo velas windsurf'})
        schema(fixture, function (err, doc) {
          should(err).be.an.Error()
          done()
        })
      })

      it('throw error if reached maximum length', function (done) {
        const title = repeat('n', 141)
        const fixture = assign({}, baseFixture, {title})
        schema(fixture, function (err, doc) {
          should(err).be.an.Error()
          done()
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>document.getElementById("idLogicV").innerHTML = "Business level version: 2017.11.08.10"

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

var rect = canvas.getBoundingClientRect();

canvas.addEventListener("touchstart", onTouchStart);
canvas.addEventListener("touchmove", onTouchMove);
canvas.addEventListener("touchend", onTouchEnd);

var touch = [];

function genRendColor(){
	var litere = "0123456789ABCDEF"
	var color = "#";
	for (var i = 0; i < 6; i++){
		color += litere[Math.floor(Math.random() * 16)];
	}
	return color;
}

function onTouchStart(e){
	e.preventDefault();
	var touches = e.changedTouches;

	for (var i = 0; i < touches.length; i++){
		touch.push({id:touches[i].identifier, color:genRendColor(), x:touches[i].pageX  - rect.left, y:touches[i].pageY - rect.left});
		context.beginPath();
		context.arc(touches[i].pageX - rect.left, touches[i].pageY - rect.top, 10, 0, 2 * Math.PI);
		context.strokeStyle = touch[touch.length - 1].color;
		context.fillStyle = touch[touch.length - 1].color;
		context.fill();
		context.stroke();
	}

}

function onTouchMove(e){
	e.preventDefault();
	var touches = e.changedTouches;

	for (var i = 0; i < touches.length; i++) {
		var color = "#FFFFFF";
		var j;
		for ( j = 0; j < touch.length; j++){
			if(touches[i].identifier == touch[j].id) {
				color = touch[j].color;
				break;
			}
		}

		context.beginPath();
		context.moveTo(touch[j].x - rect.left, touch[j].x.y - rect.top);
		context.lineWidth = 20;
		context.lineTo(touches[i].pageX - rect.left, touches[i].pageY - rect.top);
		context.arc(touches[i].pageX - rect.left, touches[i].pageY - rect.top, 10, 0, 2 * Math.PI);
		context.strokeStyle = color;
		context.fillStyle = color;
		context.fill();
		context.stroke();

		touch[j].x = touches[i].pageX;
		touch[j].y = touches[i].pageY;

	}
}

function onTouchEnd(e){
	e.preventDefault();
	var touches = e.changedTouches;

	for (var i = 0; i < touches.length; i++) {
		var color = "#FFFFFF";
		var j;
		for ( j = 0; j < touch.length; j++){
			if(touches[i].identifier == touch[j].id) {
				color = touch[j].color;
				break;
			}
		}

	}
}<commit_msg>splice<commit_after>document.getElementById("idLogicV").innerHTML = "Business level version: 2017.11.08.11"

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

var rect = canvas.getBoundingClientRect();

canvas.addEventListener("touchstart", onTouchStart);
canvas.addEventListener("touchmove", onTouchMove);
canvas.addEventListener("touchend", onTouchEnd);

var touch = [];

function genRendColor(){
	var litere = "0123456789ABCDEF"
	var color = "#";
	for (var i = 0; i < 6; i++){
		color += litere[Math.floor(Math.random() * 16)];
	}
	return color;
}

function onTouchStart(e){
	e.preventDefault();
	var touches = e.changedTouches;

	for (var i = 0; i < touches.length; i++){
		touch.push({id:touches[i].identifier, color:genRendColor(), x:touches[i].pageX  - rect.left, y:touches[i].pageY - rect.left});
		context.beginPath();
		context.arc(touches[i].pageX - rect.left, touches[i].pageY - rect.top, 10, 0, 2 * Math.PI);
		context.strokeStyle = touch[touch.length - 1].color;
		context.fillStyle = touch[touch.length - 1].color;
		context.fill();
		context.stroke();
	}

}

function onTouchMove(e){
	e.preventDefault();
	var touches = e.changedTouches;

	for (var i = 0; i < touches.length; i++) {
		var color = "#FFFFFF";
		var j;
		for ( j = 0; j < touch.length; j++){
			if(touches[i].identifier == touch[j].id) {
				color = touch[j].color;
				break;
			}
		}

		context.beginPath();
		context.moveTo(touch[j].x - rect.left, touch[j].x.y - rect.top);
		context.lineWidth = 20;
		context.lineTo(touches[i].pageX - rect.left, touches[i].pageY - rect.top);
		context.arc(touches[i].pageX - rect.left, touches[i].pageY - rect.top, 10, 0, 2 * Math.PI);
		context.strokeStyle = color;
		context.fillStyle = color;
		context.fill();
		context.stroke();

		touch[j].x = touches[i].pageX;
		touch[j].y = touches[i].pageY;

	}
}

function onTouchEnd(e){
	e.preventDefault();
	var touches = e.changedTouches;

	for (var i = 0; i < touches.length; i++) {
		var color = "#FFFFFF";
		var j;
		for ( j = 0; j < touch.length; j++){
			if(touches[i].identifier == touch[j].id) {
				color = touch[j].color;
				break;
			}
			touch.splice(j, 1);
		}

	}
}<|endoftext|>


<commit_before>angular.module('opal.controllers')
    .controller(
        'AddEpisodeCtrl',
        function($scope, $http,
                 $timeout, $routeParams,
                 $modalInstance, $rootScope,
                 Episode, FieldTranslater,
                 referencedata,
                 demographics,
                 tags){
            "use strict";
            var currentTags = [];

            _.extend($scope, referencedata.toLookuplists());

	        $scope.editing = {
                tagging: [{}],
    		        location: {},
                demographics: demographics
	        };

          $scope.editing.tagging = {};

          if(tags.tag){
            $scope.editing.tagging[tags.tag] = true;
          }

          if(tags.subtag){
            $scope.editing.tagging[tags.subtag] = true;
          }

	        $scope.save = function() {
            var toSave = FieldTranslater.jsToPatient($scope.editing)
            // this is not good
            toSave.tagging = [toSave.tagging];

		        $http.post('/api/v0.1/episode/', toSave).success(function(episode) {
			        episode = new Episode(episode);
			        $modalInstance.close(episode);
		        });
	        };

	        $scope.cancel = function() {
		        $modalInstance.close(null);
	        };

        });
<commit_msg>we save tagging back to the server as a dictionary<commit_after>angular.module('opal.controllers')
    .controller(
        'AddEpisodeCtrl',
        function($scope, $http,
                 $timeout, $routeParams,
                 $modalInstance, $rootScope,
                 Episode, FieldTranslater,
                 referencedata,
                 demographics,
                 tags){
            "use strict";
            var currentTags = [];

            _.extend($scope, referencedata.toLookuplists());

	        $scope.editing = {
                tagging: [{}],
    		        location: {},
                demographics: demographics
	        };

          $scope.editing.tagging = {};

          if(tags.tag){
            $scope.editing.tagging[tags.tag] = true;
          }

          if(tags.subtag){
            $scope.editing.tagging[tags.subtag] = true;
          }

	        $scope.save = function() {
            var toSave = FieldTranslater.jsToPatient($scope.editing)

		        $http.post('/api/v0.1/episode/', toSave).success(function(episode) {
			        episode = new Episode(episode);
			        $modalInstance.close(episode);
		        });
	        };

	        $scope.cancel = function() {
		        $modalInstance.close(null);
	        };

        });
<|endoftext|>


<commit_before>'use strict'; /*jslint es5: true, node: true, indent: 2 */
var helpers = require('./lib/helpers');
var Cache = require('./lib/cache');
var Renderer = require('./lib/renderer');

var Manager = module.exports = function(lookup, parser, globals) {
  /** Manager: the main interface to amulet rendering.

  - Creates readable streams
  - Given a lookup and parser in the constuctor, handles fetching, parsing, and caching filenames
  */
  this.cache = new Cache(lookup, parser);
  this.globals = globals;
};

Manager.prototype.stream = function(template_names, context, asap) {
  /** `stream`: take a list of templates and render them into a
  readable stream, interpolating the templates with values from context.

  `template_names`: [String]
      List of templates that TemplateLookup will use
  `context`: Object
      Dictionary to use when interpolating the templates
  `asap`: Boolean
      `asap == true` means we wait when the template requests a variable that
      is not yet available in the context. Defaults to false.
  */
  var renderer = new Renderer(template_names, this.cache, context, this.globals, asap);
  renderer.start();
  return renderer;
};

Manager.prototype.string = function(template_names, context, asap, callback) {
  /** `string`: simply reads-to-end from the stream version.

  `callback`: function(Error | undefined, String | undefined)
  */
  var chunks = [];
  this.stream(template_names, context, asap)
  .on('error', function(err) {
    callback(err);
  })
  .on('data', function(chunk) {
    chunks.push(chunk);
  })
  .on('end', function() {
    callback(undefined, chunks.join(''));
  });
};

Manager.prototype.warmup = function(filenames, callback) {
  /** warmup: hit the cache for all of the provided filenames, in series

  `filenames`: [String]
  `callback`: function(Error | null, Manager | null)
  */
  var self = this;
  helpers.eachSeries(filenames, function(filename, next) {
    self.cache.get(filename, next);
  }, function(err) {
    if (callback) {
      callback(err, self);
    }
  });
};

Manager.create = function(options, callback) {
  /** `create`: create a new manager from options. Uses the default
  string parser and filesystem lookup types.

  `options`: Object
      `root`: String
          template names are relative to this directory (default: 'templates')
      `globals`: Object
          context that is available to all templates at any time
          (default: {JSON: JSON, Number: Number, Object: Object})
      `minify`: Boolean
          minify resulting html where possible (default: false)
      `open`: String
          string that designates the beginning of a expression (default: '{{')
      `close`: String
          string that designates the end of a expression (default: '}}')

  `callback`: function(Error | undefined, Manager) | undefined
      function to call when finished creating, i.e., precompiling (optional).
      `options` is not optional if `callback` is specified.
  */
  var FilesystemLookup = require('./lib/lookups/filesystem');
  var StringParser = require('./lib/parsers/string');

  options = helpers.extend({}, {
    root: 'templates',
    globals: {
      'JSON': JSON,
      'Number': Number,
      'Object': Object,
    },
    minify: false,
    open: '{{',
    close: '}}',
  }, options);

  var lookup = new FilesystemLookup(options.root);
  var parser = new StringParser(options);
  var manager = new Manager(lookup, parser, options.globals);

  // precompile templates asynchronously
  lookup.find(function(err, filenames) {
    manager.warmup(filenames, callback);
  });

  return manager;
};

// Manager will also serve as a interface to the singleton manager, with certain defaults.
Manager.set = function(options) {
  /** set: allow setting options on the singleton, by replacing it with a new
  manager. simply runs `create` with the given options (maybe none), and uses that.
  */
  var manager = Manager.create(options);
  // create a singleton, and then attach it to the exported object, Manager
  ['stream', 'string', 'warmup'].forEach(function(property) {
    Manager[property] = manager[property].bind(manager);
  });
  return manager;
};
// create singleton manager with only the defaults
Manager.set();
<commit_msg>Expose singleton manager as .singleton property on amulet's main export, along with the .stream() and .string() helpers.<commit_after>'use strict'; /*jslint es5: true, node: true, indent: 2 */
var helpers = require('./lib/helpers');
var Cache = require('./lib/cache');
var Renderer = require('./lib/renderer');

var Manager = module.exports = function(lookup, parser, globals) {
  /** Manager: the main interface to amulet rendering.

  - Creates readable streams
  - Given a lookup and parser in the constuctor, handles fetching, parsing, and caching filenames
  */
  this.cache = new Cache(lookup, parser);
  this.globals = globals;
};

Manager.prototype.stream = function(template_names, context, asap) {
  /** `stream`: take a list of templates and render them into a
  readable stream, interpolating the templates with values from context.

  `template_names`: [String]
      List of templates that TemplateLookup will use
  `context`: Object
      Dictionary to use when interpolating the templates
  `asap`: Boolean
      `asap == true` means we wait when the template requests a variable that
      is not yet available in the context. Defaults to false.
  */
  var renderer = new Renderer(template_names, this.cache, context, this.globals, asap);
  renderer.start();
  return renderer;
};

Manager.prototype.string = function(template_names, context, asap, callback) {
  /** `string`: simply reads-to-end from the stream version.

  `callback`: function(Error | undefined, String | undefined)
  */
  var chunks = [];
  this.stream(template_names, context, asap)
  .on('error', function(err) {
    callback(err);
  })
  .on('data', function(chunk) {
    chunks.push(chunk);
  })
  .on('end', function() {
    callback(undefined, chunks.join(''));
  });
};

Manager.prototype.warmup = function(filenames, callback) {
  /** warmup: hit the cache for all of the provided filenames, in series

  `filenames`: [String]
  `callback`: function(Error | null, Manager | null)
  */
  var self = this;
  helpers.eachSeries(filenames, function(filename, next) {
    self.cache.get(filename, next);
  }, function(err) {
    if (callback) {
      callback(err, self);
    }
  });
};

Manager.create = function(options, callback) {
  /** `create`: create a new manager from options. Uses the default
  string parser and filesystem lookup types.

  `options`: Object
      `root`: String
          template names are relative to this directory (default: 'templates')
      `globals`: Object
          context that is available to all templates at any time
          (default: {JSON: JSON, Number: Number, Object: Object})
      `minify`: Boolean
          minify resulting html where possible (default: false)
      `open`: String
          string that designates the beginning of a expression (default: '{{')
      `close`: String
          string that designates the end of a expression (default: '}}')

  `callback`: function(Error | undefined, Manager) | undefined
      function to call when finished creating, i.e., precompiling (optional).
      `options` is not optional if `callback` is specified.
  */
  var FilesystemLookup = require('./lib/lookups/filesystem');
  var StringParser = require('./lib/parsers/string');

  options = helpers.extend({}, {
    root: 'templates',
    globals: {
      'JSON': JSON,
      'Number': Number,
      'Object': Object,
    },
    minify: false,
    open: '{{',
    close: '}}',
  }, options);

  var lookup = new FilesystemLookup(options.root);
  var parser = new StringParser(options);
  var manager = new Manager(lookup, parser, options.globals);

  // precompile templates asynchronously
  lookup.find(function(err, filenames) {
    manager.warmup(filenames, callback);
  });

  return manager;
};

// Manager will also serve as a interface to the singleton manager, with certain defaults.
Manager.set = function(options) {
  /** set: Creates a singleton instance and attaches it to the main export, Manager.

  Also attaches .stream() and .string() to the main export as helpers, e.g.:

      var amulet = require('amulet');
      // amulet.set({root: '/www/shared/templates'}); // <-- optional
      ...
        amulet.stream(['index.mu'], {user_id: '7009'}).pipe(res);

  Equivalent to the following, for this module (but not for others):

      amulet = require('amulet').create({root: '/path/to/templates'});
      ...

  */
  var manager = Manager.singleton = Manager.create(options);
  Manager.stream = manager.stream.bind(manager);
  Manager.string = manager.string.bind(manager);
  return manager;
};

// initialize (create) singleton manager with the default options
Manager.set();
<|endoftext|>


<commit_before>/**
 * @fileOverview This file is the core of tQuery library.
*/

/**
 * Create a tQuery element
 *
 * @class root class
 *
 * @param {} object
 * @param {THREE.Object3D} rootnode
 * @returns {tQuery.*} the tQuery object created
*/
var tQuery	= function(object, root)
{
	// support for tQuery(geometry, material)
	if( arguments.length === 2 &&
			(arguments[0] instanceof THREE.Geometry
				|| arguments[0] instanceof THREE.BufferGeometry
				|| arguments[0] instanceof tQuery.Geometry)
			&&
			(arguments[1] instanceof THREE.Material || arguments[1] instanceof tQuery.Material)
			){
		var tGeometry	= arguments[0] instanceof tQuery.Geometry ? arguments[0].get(0) : arguments[0];
		var tMaterial	= arguments[1] instanceof tQuery.Material ? arguments[1].get(0) : arguments[1];
		var tMesh	= new THREE.Mesh(tGeometry, tMaterial);
		return tQuery( tMesh );
	}

// TODO make tthat cleaner
// - there is a list of functions registered by each plugins
//   - handle() object instanceof THREE.Mesh
//   - create() return new tQuery(object)
// - this list is processed in order here

	// if the object is an array, compare only the first element
	// - up to the subconstructor to check if the whole array has proper type
	var instance	= Array.isArray(object) ? object[0] : object;

	if( instance instanceof THREE.Mesh  && tQuery.Mesh){
		return new tQuery.Mesh(object);
	}else if( instance instanceof THREE.DirectionalLight && tQuery.DirectionalLight){
		return new tQuery.DirectionalLight(object);
	}else if( instance instanceof THREE.AmbientLight && tQuery.AmbientLight){
		return new tQuery.AmbientLight(object);
	}else if( instance instanceof THREE.Light && tQuery.Light){
		return new tQuery.Light(object);

	}else if( instance instanceof THREE.Object3D  && tQuery.Object3D){
		return new tQuery.Object3D(object);
	}else if( instance instanceof THREE.Geometry && tQuery.Geometry){
		return new tQuery.Geometry(object);
	}else if( instance instanceof THREE.Material && tQuery.Material){
		return new tQuery.Material(object);
	}else if( typeof instance === "string" && tQuery.Object3D){
		return new tQuery.Object3D(object, root);
	}else{
		console.assert(false, "unsupported type")
	}
	return undefined;
};

/**
 * The version of tQuery
*/
tQuery.VERSION	= "r56.0";

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

/**
 * generic getter/setter
 *
 * @param {Object} object the object in which store the data
 * @param {String} key the key/name of the data to get/set
 * @param {*} value the value to set (optional)
 * @param {Boolean} mustNotExist if true, ensure that the key doesnt already exist, optional default to false
 *
 * @returns {*} return the value stored in this object for this key
*/
tQuery.data	= function(object, key, value, mustNotExist)
{
	// sanity check
	console.assert( object, 'invalid parameters' );
	console.assert( typeof key === 'string', 'invalid parameters');

	// init _tqData
	object['_tqData']	= object['_tqData']	|| {};
	// honor mustNotExist
	if( mustNotExist || mustNotExist === undefined ){
		console.assert(object['_tqData'][key] === undefined, "This key already exists "+key);
	}
	// set the value if any
	if( value ){
		object['_tqData'][key]	= value;
	}
	// return the value
	return object['_tqData'][key];
};

/**
 * test if the data exist
 * @param  {Object}  object the object which may or may not contain the data
 * @param  {string}  key    the key of the data
 * @return {Boolean}        true if the data exist, false otherwise
 */
tQuery.hasData	= function(object, key){
	// if there is no data at all, return false
	if( object['_tqData'] === undefined )		return false;
	// if this data doesnt exist, return false
	if( object['_tqData'][key] === undefined )	return false;
	// if all previous test passed, return true
	return true;
}

/**
 * Same as jQuery.removeData()
 *
 * @param {Boolean} mustExist if true, ensure the key does exist, default to false
*/
tQuery.removeData	= function(object, key, mustExist)
{
	// handle the 'key as Array' case
	if( key instanceof Array ){
		key.forEach(function(key){
			tQuery.removeData(object, key);
		})
		return;
	}
	// sanity check
	console.assert( typeof key === "string");
	// honor mustNotExist
	if( mustExist ){
		console.assert(object['_tqData'][key] !== undefined, "This key doesnt already exists "+key);
	}
	// do delete the key
	delete object['_tqData'][key];
	// TOTO remove object[_tqData] if empty now
}

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

/**
 * loop over a Array.
 *
 * @param {Array} arr the array to traverse.
 * @param {Function} callback the function to notify. function(element){ }.
 * 			loop interrupted if it returns false
 *
 * @returns {Boolean} return true if completed, false if interrupted
*/
tQuery.each	= function(arr, callback){
	for(var i = 0; i < arr.length; i++){
		var keepLooping	= callback(arr[i], i)
		if( keepLooping === false )	return false;
	}
	return true;
};

/**
 * precise version of Date.now() -
 * It provide submillisecond precision based on window.performance.now() when
 * available, fall back on Date.now()
 * see http://updates.html5rocks.com/2012/05/requestAnimationFrame-API-now-with-sub-millisecond-precision
*/
tQuery.now	= (function(){
	var p			= window.performance	|| {};
	if( p.now )		return function(){ return p.timing.navigationStart + p.now();		};
	else if( p.mozNow )	return function(){ return p.timing.navigationStart + p.mozNow();	};
	else if( p.webkitNow )	return function(){ return p.timing.navigationStart + p.webkitNow()	};
	else if( p.mskitNow )	return function(){ return p.timing.navigationStart + p.msNow()		};
	else if( p.okitNow )	return function(){ return p.timing.navigationStart + p.oNow()		};
	else			return function(){ return Date.now;					};
})();

/**
 * Make a child Class inherit from the parent class.
 *
 * @param {Object} childClass the child class which gonna inherit
 * @param {Object} parentClass the class which gonna be inherited
*/
tQuery.inherit	= function(childClass, parentClass){
	// trick to avoid calling parentClass constructor
	var tempFn		= function() {};
	tempFn.prototype	= parentClass.prototype;
	childClass.prototype	= new tempFn();

	childClass.parent	= parentClass.prototype;
	childClass.prototype.constructor= childClass;
};

/**
 * extend function. mainly aimed at handling default values - jme: im not sure at all it is the proper one.
 * http://jsapi.info/_/extend
 * similar to jquery one but much smaller
*/
tQuery.extend = function(obj, base, deep){
	// handle parameter polymorphism
	deep		= deep !== undefined ? deep	: true;
	var extendFn	= deep ? deepExtend : shallowExtend;
	var result	= {};
	base	&& extendFn(result, base);
	obj	&& extendFn(result, obj);
	return result;

	function shallowExtend(dst, src){
		Object.keys(src).forEach(function(key){
			dst[key]	= src[key];
		})
	};
	// from http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/
	function deepExtend(dst, src){
		for (var property in src) {
			if (src[property] && src[property].constructor && src[property].constructor === Object) {
				dst[property] = dst[property] || {};
				arguments.callee(dst[property], src[property]);
			} else {
				dst[property] = src[property];
			}
		}
		return dst;
	};
};

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

/**
 * Make an object pluginable
 *
 * @param {Object} object the object on which you mixin function
 * @param {Object} dest the object in which to register the plugin
 * @param {string} suffix the suffix to add to the function name
*/
tQuery._pluginsOn	= function(object, dest, fnNameSuffix){
	dest		= dest	|| object.prototype || object;
	fnNameSuffix	= fnNameSuffix || '';
	// sanity check
	console.assert(object['register'+fnNameSuffix] === undefined);
	console.assert(object['unregister'+fnNameSuffix] === undefined);
	console.assert(object['registered'+fnNameSuffix] === undefined);

	object['register'+fnNameSuffix]		= function(name, funct) {
		console.assert(dest[name] === undefined, 'Conflict! Already method called: ' + name);
		dest[name]	= funct;
	};
	object['unregister'+fnNameSuffix]	= function(name){
		if( dest.hasOwnProperty(name) === false ){
			throw new Error('Plugin not found: ' + name);
		}
		delete dest[name];
	};
	object['registered'+fnNameSuffix]	= function(name){
		return dest.hasOwnProperty(name) === true;
	}
};

tQuery.pluginsInstanceOn= function(klass){
	tQuery._pluginsOn(klass, undefined, 'Instance');
};
tQuery.pluginsStaticOn	= function(klass){
	tQuery._pluginsOn(klass, klass, 'Static');
};

// make it pluginable as static
tQuery.pluginsStaticOn(tQuery);

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

tQuery.mixinAttributes	= function(dstObject, properties){
	// mixin the new property
	// FIXME the inheritance should work now... not sure
	dstObject.prototype._attrProps	= tQuery.extend(dstObject.prototype._attrProps, properties);

	dstObject.prototype.attr	= function(name, args){
		// handle parameters
		if( name instanceof Object && args === undefined ){
			Object.keys(name).forEach(function(key){
				this.attr(key, name[key]);
			}.bind(this));
		}else if( typeof(name) === 'string' ){
			console.assert( Object.keys(this._attrProps).indexOf(name) !== -1, 'invalid property name:'+name);
		}else	console.assert(false, 'invalid parameter');

		// handle setter
		if( args !== undefined ){
			var convertFn	= this._attrProps[name];
			var value	= convertFn.apply(convertFn, args);
			this.each(function(element){
				element[name]	= value;
			})
			return this;
		}
		// handle getter
		if( this.length === 0 )	return undefined
		var element	= this.get(0);
		return element[name];
	};

	// add shortcuts
	Object.keys(properties).forEach(function(name){
		dstObject.prototype[name]	= function(/* arguments */){
			return this.attr(name, arguments);
		};
	}.bind(this));
};

//////////////////////////////////////////////////////////////////////////////////
//		put some helpers						//
//////////////////////////////////////////////////////////////////////////////////

/**
 * Flow control - from https://github.com/jeromeetienne/gowiththeflow.js
*/
tQuery.Flow	= function(){
	var self, stack = [], timerId = setTimeout(function(){ timerId = null; self._next(); }, 0);
	return self = {
		destroy	: function(){ timerId && clearTimeout(timerId);	},
		par	: function(callback, isSeq){
			if(isSeq || !(stack[stack.length-1] instanceof Array)) stack.push([]);
			stack[stack.length-1].push(callback);
			return self;
		},seq	: function(callback){ return self.par(callback, true);	},
		_next	: function(err, result){
			var errors = [], results = [], callbacks = stack.shift() || [], nbReturn = callbacks.length, isSeq = nbReturn == 1;
			callbacks && callbacks.forEach(function(fct, index){
				fct(function(error, result){
					errors[index]	= error;
					results[index]	= result;
					if(--nbReturn == 0)	self._next(isSeq?errors[0]:errors, isSeq?results[0]:results)
				}, err, result)
			})
		}
	}
};

/**
 * microevents.js - https://github.com/jeromeetienne/microevent.js
*/
tQuery.MicroeventMixin	= function(destObj){
	var bind	= function(event, fct){
		if(this._events === undefined) 	this._events	= {};
		this._events[event] = this._events[event]	|| [];
		this._events[event].push(fct);
		return fct;
	};
	var unbind	= function(event, fct){
		if(this._events === undefined) 	this._events	= {};
		if( event in this._events === false  )	return;
		this._events[event].splice(this._events[event].indexOf(fct), 1);
	};
	var trigger	= function(event /* , args... */){
		if(this._events === undefined) 	this._events	= {};
		if( this._events[event] === undefined )	return;
		var tmpArray	= this._events[event].slice();
		for(var i = 0; i < tmpArray.length; i++){
			var result	= tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1))
			if( result !== undefined )	return result;
		}
		return undefined;
	};

	// backward compatibility
	destObj.bind	= bind;
	destObj.unbind	= unbind;
	destObj.trigger	= trigger;

	destObj.addEventListener	= function(event, fct){
		this.bind(event, fct)
		return this;	// for chained API
	}
	destObj.removeEventListener	= function(event, fct){
		this.unbind(event, fct)
		return this;	// for chained API
	}
	destObj.dispatchEvent		= function(event /* , args... */){
		return this.trigger.apply(this, arguments)
	}
};

/**
 * https://github.com/jeromeetienne/MicroCache.js
*/
tQuery.MicroCache	= function(){
	var _values	= {};
	return {
		get	: function(key){ return _values[key];	},
		contains: function(key){ return key in _values;	},
		remove	: function(key){ delete _values[key];	},
		set	: function(key, value){	_values[key] = value;},
		values	: function(){ return _values;	},
		getSet	: function(key, value){
			if( !this.contains(key) ){
				this.set(key, typeof value == 'function' ? value() : value )
			}
			return this.get(key);
		}
	}
}

<commit_msg>bug fix in tQuery.data() setter sanity check<commit_after>/**
 * @fileOverview This file is the core of tQuery library.
*/

/**
 * Create a tQuery element
 *
 * @class root class
 *
 * @param {} object
 * @param {THREE.Object3D} rootnode
 * @returns {tQuery.*} the tQuery object created
*/
var tQuery	= function(object, root)
{
	// support for tQuery(geometry, material)
	if( arguments.length === 2 &&
			(arguments[0] instanceof THREE.Geometry
				|| arguments[0] instanceof THREE.BufferGeometry
				|| arguments[0] instanceof tQuery.Geometry)
			&&
			(arguments[1] instanceof THREE.Material || arguments[1] instanceof tQuery.Material)
			){
		var tGeometry	= arguments[0] instanceof tQuery.Geometry ? arguments[0].get(0) : arguments[0];
		var tMaterial	= arguments[1] instanceof tQuery.Material ? arguments[1].get(0) : arguments[1];
		var tMesh	= new THREE.Mesh(tGeometry, tMaterial);
		return tQuery( tMesh );
	}

// TODO make tthat cleaner
// - there is a list of functions registered by each plugins
//   - handle() object instanceof THREE.Mesh
//   - create() return new tQuery(object)
// - this list is processed in order here

	// if the object is an array, compare only the first element
	// - up to the subconstructor to check if the whole array has proper type
	var instance	= Array.isArray(object) ? object[0] : object;

	if( instance instanceof THREE.Mesh  && tQuery.Mesh){
		return new tQuery.Mesh(object);
	}else if( instance instanceof THREE.DirectionalLight && tQuery.DirectionalLight){
		return new tQuery.DirectionalLight(object);
	}else if( instance instanceof THREE.AmbientLight && tQuery.AmbientLight){
		return new tQuery.AmbientLight(object);
	}else if( instance instanceof THREE.Light && tQuery.Light){
		return new tQuery.Light(object);

	}else if( instance instanceof THREE.Object3D  && tQuery.Object3D){
		return new tQuery.Object3D(object);
	}else if( instance instanceof THREE.Geometry && tQuery.Geometry){
		return new tQuery.Geometry(object);
	}else if( instance instanceof THREE.Material && tQuery.Material){
		return new tQuery.Material(object);
	}else if( typeof instance === "string" && tQuery.Object3D){
		return new tQuery.Object3D(object, root);
	}else{
		console.assert(false, "unsupported type")
	}
	return undefined;
};

/**
 * The version of tQuery
*/
tQuery.VERSION	= "r56.0";

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

/**
 * generic getter/setter
 *
 * @param {Object} object the object in which store the data
 * @param {String} key the key/name of the data to get/set
 * @param {*} value the value to set (optional)
 * @param {Boolean} mustNotExist if true, ensure that the key doesnt already exist, optional default to false
 *
 * @returns {*} return the value stored in this object for this key
*/
tQuery.data	= function(object, key, value, mustNotExist)
{
	// sanity check
	console.assert( object, 'invalid parameters' );
	console.assert( typeof key === 'string', 'invalid parameters');
	// handle default arguments values
	if( mustNotExist === undefined && value !== undefined )	mustNotExist = true;

	// init _tqData
	object['_tqData']	= object['_tqData']	|| {};
	// honor mustNotExist
	if( mustNotExist ){
		console.assert(object['_tqData'][key] === undefined, "This key already exists "+key);
	}
	// set the value if any
	if( value ){
		object['_tqData'][key]	= value;
	}
	// return the value
	return object['_tqData'][key];
};

/**
 * test if the data exist
 * @param  {Object}  object the object which may or may not contain the data
 * @param  {string}  key    the key of the data
 * @return {Boolean}        true if the data exist, false otherwise
 */
tQuery.hasData	= function(object, key){
	// if there is no data at all, return false
	if( object['_tqData'] === undefined )		return false;
	// if this data doesnt exist, return false
	if( object['_tqData'][key] === undefined )	return false;
	// if all previous test passed, return true
	return true;
}

/**
 * Same as jQuery.removeData()
 *
 * @param {Boolean} mustExist if true, ensure the key does exist, default to false
*/
tQuery.removeData	= function(object, key, mustExist)
{
	// handle the 'key as Array' case
	if( key instanceof Array ){
		key.forEach(function(key){
			tQuery.removeData(object, key);
		})
		return;
	}
	// sanity check
	console.assert( typeof key === "string");
	// honor mustNotExist
	if( mustExist ){
		console.assert(object['_tqData'][key] !== undefined, "This key doesnt already exists "+key);
	}
	// do delete the key
	delete object['_tqData'][key];
	// TOTO remove object[_tqData] if empty now
}

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

/**
 * loop over a Array.
 *
 * @param {Array} arr the array to traverse.
 * @param {Function} callback the function to notify. function(element){ }.
 * 			loop interrupted if it returns false
 *
 * @returns {Boolean} return true if completed, false if interrupted
*/
tQuery.each	= function(arr, callback){
	for(var i = 0; i < arr.length; i++){
		var keepLooping	= callback(arr[i], i)
		if( keepLooping === false )	return false;
	}
	return true;
};

/**
 * precise version of Date.now() -
 * It provide submillisecond precision based on window.performance.now() when
 * available, fall back on Date.now()
 * see http://updates.html5rocks.com/2012/05/requestAnimationFrame-API-now-with-sub-millisecond-precision
*/
tQuery.now	= (function(){
	var p			= window.performance	|| {};
	if( p.now )		return function(){ return p.timing.navigationStart + p.now();		};
	else if( p.mozNow )	return function(){ return p.timing.navigationStart + p.mozNow();	};
	else if( p.webkitNow )	return function(){ return p.timing.navigationStart + p.webkitNow()	};
	else if( p.mskitNow )	return function(){ return p.timing.navigationStart + p.msNow()		};
	else if( p.okitNow )	return function(){ return p.timing.navigationStart + p.oNow()		};
	else			return function(){ return Date.now;					};
})();

/**
 * Make a child Class inherit from the parent class.
 *
 * @param {Object} childClass the child class which gonna inherit
 * @param {Object} parentClass the class which gonna be inherited
*/
tQuery.inherit	= function(childClass, parentClass){
	// trick to avoid calling parentClass constructor
	var tempFn		= function() {};
	tempFn.prototype	= parentClass.prototype;
	childClass.prototype	= new tempFn();

	childClass.parent	= parentClass.prototype;
	childClass.prototype.constructor= childClass;
};

/**
 * extend function. mainly aimed at handling default values - jme: im not sure at all it is the proper one.
 * http://jsapi.info/_/extend
 * similar to jquery one but much smaller
*/
tQuery.extend = function(obj, base, deep){
	// handle parameter polymorphism
	deep		= deep !== undefined ? deep	: true;
	var extendFn	= deep ? deepExtend : shallowExtend;
	var result	= {};
	base	&& extendFn(result, base);
	obj	&& extendFn(result, obj);
	return result;

	function shallowExtend(dst, src){
		Object.keys(src).forEach(function(key){
			dst[key]	= src[key];
		})
	};
	// from http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/
	function deepExtend(dst, src){
		for (var property in src) {
			if (src[property] && src[property].constructor && src[property].constructor === Object) {
				dst[property] = dst[property] || {};
				arguments.callee(dst[property], src[property]);
			} else {
				dst[property] = src[property];
			}
		}
		return dst;
	};
};

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

/**
 * Make an object pluginable
 *
 * @param {Object} object the object on which you mixin function
 * @param {Object} dest the object in which to register the plugin
 * @param {string} suffix the suffix to add to the function name
*/
tQuery._pluginsOn	= function(object, dest, fnNameSuffix){
	dest		= dest	|| object.prototype || object;
	fnNameSuffix	= fnNameSuffix || '';
	// sanity check
	console.assert(object['register'+fnNameSuffix] === undefined);
	console.assert(object['unregister'+fnNameSuffix] === undefined);
	console.assert(object['registered'+fnNameSuffix] === undefined);

	object['register'+fnNameSuffix]		= function(name, funct) {
		console.assert(dest[name] === undefined, 'Conflict! Already method called: ' + name);
		dest[name]	= funct;
	};
	object['unregister'+fnNameSuffix]	= function(name){
		if( dest.hasOwnProperty(name) === false ){
			throw new Error('Plugin not found: ' + name);
		}
		delete dest[name];
	};
	object['registered'+fnNameSuffix]	= function(name){
		return dest.hasOwnProperty(name) === true;
	}
};

tQuery.pluginsInstanceOn= function(klass){
	tQuery._pluginsOn(klass, undefined, 'Instance');
};
tQuery.pluginsStaticOn	= function(klass){
	tQuery._pluginsOn(klass, klass, 'Static');
};

// make it pluginable as static
tQuery.pluginsStaticOn(tQuery);

//////////////////////////////////////////////////////////////////////////////////
//										//
//////////////////////////////////////////////////////////////////////////////////

tQuery.mixinAttributes	= function(dstObject, properties){
	// mixin the new property
	// FIXME the inheritance should work now... not sure
	dstObject.prototype._attrProps	= tQuery.extend(dstObject.prototype._attrProps, properties);

	dstObject.prototype.attr	= function(name, args){
		// handle parameters
		if( name instanceof Object && args === undefined ){
			Object.keys(name).forEach(function(key){
				this.attr(key, name[key]);
			}.bind(this));
		}else if( typeof(name) === 'string' ){
			console.assert( Object.keys(this._attrProps).indexOf(name) !== -1, 'invalid property name:'+name);
		}else	console.assert(false, 'invalid parameter');

		// handle setter
		if( args !== undefined ){
			var convertFn	= this._attrProps[name];
			var value	= convertFn.apply(convertFn, args);
			this.each(function(element){
				element[name]	= value;
			})
			return this;
		}
		// handle getter
		if( this.length === 0 )	return undefined
		var element	= this.get(0);
		return element[name];
	};

	// add shortcuts
	Object.keys(properties).forEach(function(name){
		dstObject.prototype[name]	= function(/* arguments */){
			return this.attr(name, arguments);
		};
	}.bind(this));
};

//////////////////////////////////////////////////////////////////////////////////
//		put some helpers						//
//////////////////////////////////////////////////////////////////////////////////

/**
 * Flow control - from https://github.com/jeromeetienne/gowiththeflow.js
*/
tQuery.Flow	= function(){
	var self, stack = [], timerId = setTimeout(function(){ timerId = null; self._next(); }, 0);
	return self = {
		destroy	: function(){ timerId && clearTimeout(timerId);	},
		par	: function(callback, isSeq){
			if(isSeq || !(stack[stack.length-1] instanceof Array)) stack.push([]);
			stack[stack.length-1].push(callback);
			return self;
		},seq	: function(callback){ return self.par(callback, true);	},
		_next	: function(err, result){
			var errors = [], results = [], callbacks = stack.shift() || [], nbReturn = callbacks.length, isSeq = nbReturn == 1;
			callbacks && callbacks.forEach(function(fct, index){
				fct(function(error, result){
					errors[index]	= error;
					results[index]	= result;
					if(--nbReturn == 0)	self._next(isSeq?errors[0]:errors, isSeq?results[0]:results)
				}, err, result)
			})
		}
	}
};

/**
 * microevents.js - https://github.com/jeromeetienne/microevent.js
*/
tQuery.MicroeventMixin	= function(destObj){
	var bind	= function(event, fct){
		if(this._events === undefined) 	this._events	= {};
		this._events[event] = this._events[event]	|| [];
		this._events[event].push(fct);
		return fct;
	};
	var unbind	= function(event, fct){
		if(this._events === undefined) 	this._events	= {};
		if( event in this._events === false  )	return;
		this._events[event].splice(this._events[event].indexOf(fct), 1);
	};
	var trigger	= function(event /* , args... */){
		if(this._events === undefined) 	this._events	= {};
		if( this._events[event] === undefined )	return;
		var tmpArray	= this._events[event].slice();
		for(var i = 0; i < tmpArray.length; i++){
			var result	= tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1))
			if( result !== undefined )	return result;
		}
		return undefined;
	};

	// backward compatibility
	destObj.bind	= bind;
	destObj.unbind	= unbind;
	destObj.trigger	= trigger;

	destObj.addEventListener	= function(event, fct){
		this.bind(event, fct)
		return this;	// for chained API
	}
	destObj.removeEventListener	= function(event, fct){
		this.unbind(event, fct)
		return this;	// for chained API
	}
	destObj.dispatchEvent		= function(event /* , args... */){
		return this.trigger.apply(this, arguments)
	}
};

/**
 * https://github.com/jeromeetienne/MicroCache.js
*/
tQuery.MicroCache	= function(){
	var _values	= {};
	return {
		get	: function(key){ return _values[key];	},
		contains: function(key){ return key in _values;	},
		remove	: function(key){ delete _values[key];	},
		set	: function(key, value){	_values[key] = value;},
		values	: function(){ return _values;	},
		getSet	: function(key, value){
			if( !this.contains(key) ){
				this.set(key, typeof value == 'function' ? value() : value )
			}
			return this.get(key);
		}
	}
}

<|endoftext|>


<commit_before>var askeecsControllers = angular.module('askeecsControllers', ['ngCookies']);

askeecsControllers.controller('QuestionListCtrl', ['$scope', 'Questions',
	function ($scope, Questions) {
		Questions.List()
			.success(function (questions) {
				$scope.questions = questions;
			})
			.error(function (error) {
				$scope.error = 'Unable to load questions';
			});
	}
]);

askeecsControllers.controller('RegisterCtrl', ['$scope', '$http', '$location', 'AuthService',
	function ($scope, $http, $location, AuthService) {
		var credentials = { "Username": "", "Password": "", "CPassword": "" }

		$scope.credentials = credentials;
		$scope.processForm = function () {

			// Make sure they have entered a password that matches
			if($scope.credentials.Password != $scope.credentials.CPassword) {
				console.log("Missed matched password");
				return;
			}

			// We don't need this to be passed along
			delete $scope.credentials.CPassword;

			// Register the user and redirect them to the login page
			AuthService.register($scope.credentials, function () {
				$location.path("/login");
			});

			// Make sure we wipe out the credentials
			$scope.credentials = credentials;

		}
	}
]);

askeecsControllers.controller('LoginCtrl', ['$scope', '$http', '$location', 'AuthService',
	function ($scope, $http, $location, AuthService) {
		var credentials = { "Username": "", "Password": "", "Salt": "" }

		$scope.credentials = credentials
		$scope.processForm = function () {

			// Log the user in and direct them tot he home page
			AuthService.login($scope.credentials, function () {
				$location.path("/");
			});

			// Make sure we wipe out the credentials
			$scope.credentials = credentials
		}
	}
]);

askeecsControllers.controller('QuestionAskCtrl', ['$scope', '$http', '$window', '$sce', '$location',
	function ($scope, $http, $window, $sce, $location) {
		var question = {"markdown" : "", "title" : "", "tags" : ""}

		$scope.question = question;

		$scope.md2Html = function() {
			var src			= $scope.markdown || ""
			var html		= $window.marked(src);
			$scope.htmlSafe = $sce.trustAsHtml(html);
		}

		$scope.processForm = function () {

			// Remove any previous error statements
			$scope.error = {}

			// Default to a non error state
			var err = false;

			if ($scope.question.markdown.length < 50)
			{
				$scope.error.markdown = "Your question must be 50 characters or more."
				err = true;
			}

			if ($scope.question.title.length == 0)
			{
				$scope.error.title = "You must enter a title."
				err = true;
			}

			if ($scope.question.tags.length == 0)
			{
				$scope.error.tags = "You must have at least one tag."
				err = true;
			}

			if (err) {
				return;
			}

			$http({
				method: 'POST',
				url: '/q',
				data: {Title:$scope.question.title, Body: $scope.question.markdown, Tags: $scope.question.tags.split(' ')}
			}).success(function(data) {
				// TODO: this should be a JSON response
				$location.path("/questions/"+data);
			});
			// TODO: Failure
		}

	}
]);

askeecsControllers.controller('QuestionDetailCtrl', ['$scope', '$routeParams', '$http', '$window', '$sce',
	function ($scope, $routeParams, $http, $window, $sce) {
		$scope.comment = { "Body" : "" }
		$scope.response = { "Body" : "" }

		$http.get('/q/' + $routeParams.questionId).success(function(data) {
			$scope.question = data;
			console.log(data)
		});

		$scope.voteUp = function () {
			$http({
				method: 'GET',
				url: '/q/' + $scope.question.ID + '/vote/up',
				data: {}
			}).success(function(data) {
				$scope.question.Upvotes = data.Upvotes
			});
		}

		$scope.voteDown = function () {
			$http({
				method: 'GET',
				url: '/q/' + $scope.question.ID + '/vote/down',
				data: {}
			}).success(function(data) {
				$scope.question.Downvotes = data.Downvotes
			});
		}

		$scope.markdown="";
		$scope.md2Html = function() {
			var src = $scope.response.Body || ""
			$scope.html = $window.marked(src);
			$scope.htmlSafe = $sce.trustAsHtml($scope.html);
		}

		// Can a comment have this own controller and it's own scope?
		$scope.processComment = function () {
			delete $scope.errorComment;

			var err = false;

			if ( $scope.comment.Body.length < 15 )
			{
				$scope.errorComment = "Your comment must be at least 15 characters"
				err = true;
			}

			if (err) return;

			$http({
				method: 'post',
				url: '/q/' + $scope.question.ID + '/comment/',
				data: $scope.comment
			}).success(function(data) {
				delete $scope.scomment;
				$scope.question.Comments.push(data);
			});
		}

		$scope.processForm = function () {
			console.log($scope.response.Body);
			delete $scope.errorMarkdown;

			var err = false;

			if ($scope.response.Body.length < 50)
			{
				$scope.errorMarkdown = "Your response must be 50 characters or more."
				err = true;
			}

			if (err) {
				return;
			}

			$http({
				method: 'post',
				url: '/q/' + $scope.question.ID + '/response/',
				data: $scope.response
			}).success(function(data) {
				$scope.question.Responses.push(data);
			});
		}
	}
]);
<commit_msg>Fixed live preview<commit_after>var askeecsControllers = angular.module('askeecsControllers', ['ngCookies']);

askeecsControllers.controller('QuestionListCtrl', ['$scope', 'Questions',
	function ($scope, Questions) {
		Questions.List()
			.success(function (questions) {
				$scope.questions = questions;
			})
			.error(function (error) {
				$scope.error = 'Unable to load questions';
			});
	}
]);

askeecsControllers.controller('RegisterCtrl', ['$scope', '$http', '$location', 'AuthService',
	function ($scope, $http, $location, AuthService) {
		var credentials = { "Username": "", "Password": "", "CPassword": "" }

		$scope.credentials = credentials;
		$scope.processForm = function () {

			// Make sure they have entered a password that matches
			if($scope.credentials.Password != $scope.credentials.CPassword) {
				console.log("Missed matched password");
				return;
			}

			// We don't need this to be passed along
			delete $scope.credentials.CPassword;

			// Register the user and redirect them to the login page
			AuthService.register($scope.credentials, function () {
				$location.path("/login");
			});

			// Make sure we wipe out the credentials
			$scope.credentials = credentials;

		}
	}
]);

askeecsControllers.controller('LoginCtrl', ['$scope', '$http', '$location', 'AuthService',
	function ($scope, $http, $location, AuthService) {
		var credentials = { "Username": "", "Password": "", "Salt": "" }

		$scope.credentials = credentials
		$scope.processForm = function () {

			// Log the user in and direct them tot he home page
			AuthService.login($scope.credentials, function () {
				$location.path("/");
			});

			// Make sure we wipe out the credentials
			$scope.credentials = credentials
		}
	}
]);

askeecsControllers.controller('QuestionAskCtrl', ['$scope', '$http', '$window', '$sce', '$location',
	function ($scope, $http, $window, $sce, $location) {
		var question = {"markdown" : "", "title" : "", "tags" : ""}

		$scope.question = question;

		$scope.md2Html = function() {
			var src			= $scope.question.markdown || ""
			var html		= $window.marked(src);
			$scope.htmlSafe = $sce.trustAsHtml(html);
		}

		$scope.processForm = function () {

			// Remove any previous error statements
			$scope.error = {}

			// Default to a non error state
			var err = false;

			if ($scope.question.markdown.length < 50)
			{
				$scope.error.markdown = "Your question must be 50 characters or more."
				err = true;
			}

			if ($scope.question.title.length == 0)
			{
				$scope.error.title = "You must enter a title."
				err = true;
			}

			if ($scope.question.tags.length == 0)
			{
				$scope.error.tags = "You must have at least one tag."
				err = true;
			}

			if (err) {
				return;
			}

			$http({
				method: 'POST',
				url: '/q',
				data: {Title:$scope.question.title, Body: $scope.question.markdown, Tags: $scope.question.tags.split(' ')}
			}).success(function(data) {
				// TODO: this should be a JSON response
				$location.path("/questions/"+data);
			});
			// TODO: Failure
		}

	}
]);

askeecsControllers.controller('QuestionDetailCtrl', ['$scope', '$routeParams', '$http', '$window', '$sce',
	function ($scope, $routeParams, $http, $window, $sce) {
		$scope.comment = { "Body" : "" }
		$scope.response = { "Body" : "" }

		$http.get('/q/' + $routeParams.questionId).success(function(data) {
			$scope.question = data;
			console.log(data)
		});

		$scope.voteUp = function () {
			$http({
				method: 'GET',
				url: '/q/' + $scope.question.ID + '/vote/up',
				data: {}
			}).success(function(data) {
				$scope.question.Upvotes = data.Upvotes
			});
		}

		$scope.voteDown = function () {
			$http({
				method: 'GET',
				url: '/q/' + $scope.question.ID + '/vote/down',
				data: {}
			}).success(function(data) {
				$scope.question.Downvotes = data.Downvotes
			});
		}

		$scope.markdown="";
		$scope.md2Html = function() {
			var src = $scope.response.Body || ""
			$scope.html = $window.marked(src);
			$scope.htmlSafe = $sce.trustAsHtml($scope.html);
		}

		// Can a comment have this own controller and it's own scope?
		$scope.processComment = function () {
			delete $scope.errorComment;

			var err = false;

			if ( $scope.comment.Body.length < 15 )
			{
				$scope.errorComment = "Your comment must be at least 15 characters"
				err = true;
			}

			if (err) return;

			$http({
				method: 'post',
				url: '/q/' + $scope.question.ID + '/comment/',
				data: $scope.comment
			}).success(function(data) {
				delete $scope.scomment;
				$scope.question.Comments.push(data);
			});
		}

		$scope.processForm = function () {
			console.log($scope.response.Body);
			delete $scope.errorMarkdown;

			var err = false;

			if ($scope.response.Body.length < 50)
			{
				$scope.errorMarkdown = "Your response must be 50 characters or more."
				err = true;
			}

			if (err) {
				return;
			}

			$http({
				method: 'post',
				url: '/q/' + $scope.question.ID + '/response/',
				data: $scope.response
			}).success(function(data) {
				$scope.question.Responses.push(data);
			});
		}
	}
]);
<|endoftext|>


<commit_before><commit_msg>Added base methods: mixin, clone, and inherit<commit_after>exports.mixin = function(object, base) {
  for (var property in base) {
    object[property] = base[property];
  }
};

exports.clone = function(object) {
  var object_clone = {};
  for (var property in object) {
    object_clone[property] = object[property];
  }
  return object_clone;
};

exports.inherit = function(proto) {
  if (Object.create) {            // Use ES5 Object.create
    return Object.create(proto);
  }
  else if ({}.__proto__) {        // Use non-standard property
    var object = {};
    object.__proto__ = proto;
    return object;
  }
  else {                          // Fallback to using constructor
    var Constructor = function(){};
    Constructor.prototype = proto;
    return new Constructor();
  }
};
<|endoftext|>


<commit_before>// This seems quite ugly as it is mixed into two models. What's a better
// approach for url generation helpers in sproutcore?

Travis.Helpers.Urls = {
  urlCurrent: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'ul') {
      return '#!/' + this.getPath('content.slug');
    } else {
      return '#!/' + this.getPath('repository.slug');
    }
  }.property('content', 'repository'),

  urlBuilds: function() {
    return '#!/' + this.getPath('repository.slug') + '/builds';
  }.property('repository'),

  urlParentBuild: function() {
    return '#!/' + this.getPath('repository.slug') + '/builds/' + this.getPath('build.parentId');
  }.property('repository', 'build'),

  urlBuild: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'tbody') {
      var slug = this.getPath('parentView.parentView.parentView.repository.slug') || this.getPath('parentView.parentView.repository.slug');
      return '#!/' + slug + '/builds/' + this.getPath('content.id');
    } else {
      return '#!/' + this.getPath('repository.slug') + '/builds/' + this.getPath('build.id');
    }
  }.property('repository.slug', 'build.id'),

  urlLastBuild: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'ul') {
      return '#!/' + this.getPath('content.slug') + '/builds/' + this.getPath('content.lastBuildId');
    } else {
      return '#!/' + this.getPath('repository.slug') + '/builds/' + this.getPath('repository.lastBuildId');
    }
  }.property('repository'),

  urlGithubRepository: function() {
    return 'http://github.com/' + this.getPath('repository.slug');
  }.property('repository.slug'),

  urlGithubCommit: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'tbody') {
      var slug = this.getPath('parentView.parentView.parentView.repository.slug') || this.getPath('parentView.parentView.repository.slug');
      return '#!/' + slug + '/builds/' + this.getPath('content.id');
    } else {
      return 'http://github.com/' + this.getPath('repository.slug') + '/commit/' + this.getPath('build.commit');
    }
  }.property('repository.slug', 'build.commit'),

  urlGithubWatchers: function() {
    return 'http://github.com/' + this.getPath('repository.slug') + '/watchers';
  }.property('repository.slug'),

  urlGithubNetwork: function() {
    return 'http://github.com/' + this.getPath('repository.slug') + '/network';
  }.property('repository.slug'),

  urlAuthor: function() {
    return 'mailto:' + this.getPath('build.authorEmail');
  }.property('build.authorEmail'),

  urlCommitter: function() {
    return 'mailto:' + this.getPath('build.committerEmail');
  }.property('build.committerEmail'),
};
<commit_msg>watch underscored here :/<commit_after>// This seems quite ugly as it is mixed into two models. What's a better
// approach for url generation helpers in sproutcore?

Travis.Helpers.Urls = {
  urlCurrent: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'ul') {
      return '#!/' + this.getPath('content.slug');
    } else {
      return '#!/' + this.getPath('repository.slug');
    }
  }.property('content', 'repository'),

  urlBuilds: function() {
    return '#!/' + this.getPath('repository.slug') + '/builds';
  }.property('repository'),

  urlParentBuild: function() {
    return '#!/' + this.getPath('repository.slug') + '/builds/' + this.getPath('build.parentId');
  }.property('repository.slug', 'build.parent_id'),

  urlBuild: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'tbody') {
      var slug = this.getPath('parentView.parentView.parentView.repository.slug') || this.getPath('parentView.parentView.repository.slug');
      return '#!/' + slug + '/builds/' + this.getPath('content.id');
    } else {
      return '#!/' + this.getPath('repository.slug') + '/builds/' + this.getPath('build.id');
    }
  }.property('repository.slug', 'build.id'),

  urlLastBuild: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'ul') {
      return '#!/' + this.getPath('content.slug') + '/builds/' + this.getPath('content.lastBuildId');
    } else {
      return '#!/' + this.getPath('repository.slug') + '/builds/' + this.getPath('repository.lastBuildId');
    }
  }.property('repository'),

  urlGithubRepository: function() {
    return 'http://github.com/' + this.getPath('repository.slug');
  }.property('repository.slug'),

  urlGithubCommit: function() {
    // OMFG, HAX.
    // I'm not able to bind the build and repository to the item views of the matrix collection
    // view properly. See templates/builds/show.jst.hjs
    if(this.getPath('parentView.tagName') == 'tbody') {
      var slug = this.getPath('parentView.parentView.parentView.repository.slug') || this.getPath('parentView.parentView.repository.slug');
      return '#!/' + slug + '/builds/' + this.getPath('content.id');
    } else {
      return 'http://github.com/' + this.getPath('repository.slug') + '/commit/' + this.getPath('build.commit');
    }
  }.property('repository.slug', 'build.commit'),

  urlGithubWatchers: function() {
    return 'http://github.com/' + this.getPath('repository.slug') + '/watchers';
  }.property('repository.slug'),

  urlGithubNetwork: function() {
    return 'http://github.com/' + this.getPath('repository.slug') + '/network';
  }.property('repository.slug'),

  urlAuthor: function() {
    return 'mailto:' + this.getPath('build.authorEmail');
  }.property('build.authorEmail'),

  urlCommitter: function() {
    return 'mailto:' + this.getPath('build.committerEmail');
  }.property('build.committerEmail'),
};
<|endoftext|>