<commit_before>'use strict';

/**
 * Module dependencies
 */

// Node.js core
const path = require('path');

// Public node modules.
const _ = require('lodash');

// Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate
const isNumeric = (value) => {
  return !isNaN(parseFloat(value)) && isFinite(value);
};

/* eslint-disable prefer-template */
/*
 * Set of utils for models
 */

module.exports = {

  /**
   * Initialize to prevent some mistakes
   */

  initialize: cb => {
    cb();
  },

  /**
   * Find primary key per ORM
   */

  getPK: function (collectionIdentity, collection, models) {
    if (_.isString(collectionIdentity)) {
      const ORM = this.getORM(collectionIdentity);

      try {
        const GraphQLFunctions = require(path.resolve(strapi.config.appPath, 'node_modules', 'strapi-' + ORM, 'lib', 'utils'));

        if (!_.isUndefined(GraphQLFunctions)) {
          return GraphQLFunctions.getPK(collectionIdentity, collection, models || strapi.models);
        }
      } catch (err) {
        return undefined;
      }
    }

    return undefined;
  },

  /**
   * Retrieve the value based on the primary key
   */

  getValuePrimaryKey: (value, defaultKey) => {
    return value[defaultKey] || value.id || value._id;
  },

  /**
   * Find primary key per ORM
   */

  getCount: function (collectionIdentity) {
    if (_.isString(collectionIdentity)) {
      const ORM = this.getORM(collectionIdentity);

      try {
        const ORMFunctions = require(path.resolve(strapi.config.appPath, 'node_modules', 'strapi-' + ORM, 'lib', 'utils'));

        if (!_.isUndefined(ORMFunctions)) {
          return ORMFunctions.getCount(collectionIdentity);
        }
      } catch (err) {
        return undefined;
      }
    }

    return undefined;
  },

  /**
   * Find relation nature with verbose
   */

  getNature: (association, key, models, currentModelName) => {
    try {
      const types = {
        current: '',
        other: ''
      };

      if (_.isUndefined(models)) {
        models = association.plugin ? strapi.plugins[association.plugin].models : strapi.models;
      }

      if ((association.hasOwnProperty('collection') && association.collection === '*') || (association.hasOwnProperty('model') && association.model === '*')) {
        if (association.model) {
          types.current = 'morphToD';
        } else {
          types.current = 'morphTo';
        }

        const flattenedPluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
          Object.keys(strapi.plugins[current].models).forEach((model) => {
            acc[`${current}_${model}`] = strapi.plugins[current].models[model];
          });

          return acc;
        }, {});

        const allModels = _.merge({}, strapi.models, flattenedPluginsModels);

        // We have to find if they are a model linked to this key
        _.forIn(allModels, model => {
          _.forIn(model.attributes, attribute => {
            if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.model === currentModelName) {
              if (attribute.hasOwnProperty('collection')) {
                types.other = 'collection';

                // Break loop
                return false;
              } else if (attribute.hasOwnProperty('model')) {
                types.other = 'model';

                // Break loop
                return false;
              }
            }
          });
        });
      } else if (association.hasOwnProperty('via') && association.hasOwnProperty('collection')) {
        const relatedAttribute = models[association.collection].attributes[association.via];

        if (!relatedAttribute) {
          throw new Error(`The attribute \`${association.via}\` is missing in the model ${_.upperFirst(association.collection)} ${association.plugin ? '(plugin - ' + association.plugin + ')' : '' }`);
        }

        types.current = 'collection';

        if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.collection !== '*' && relatedAttribute.hasOwnProperty('via')) {
          types.other = 'collection';
        } else if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.collection !== '*' && !relatedAttribute.hasOwnProperty('via')) {
          types.other = 'collectionD';
        } else if (relatedAttribute.hasOwnProperty('model') && relatedAttribute.model !== '*') {
          types.other = 'model';
        } else if (relatedAttribute.hasOwnProperty('collection') || relatedAttribute.hasOwnProperty('model')) {
          types.other = 'morphTo';
        }
      } else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) {
        types.current = 'modelD';

        // We have to find if they are a model linked to this key
        const model = models[association.model];
        const attribute = model.attributes[association.via];

        if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection') && attribute.collection !== '*') {
          types.other = 'collection';
        } else if (attribute.hasOwnProperty('model') && attribute.model !== '*') {
          types.other = 'model';
        } else if (attribute.hasOwnProperty('collection') || attribute.hasOwnProperty('model')) {
          types.other = 'morphTo';
        }
      } else if (association.hasOwnProperty('model')) {
        types.current = 'model';

        // We have to find if they are a model linked to this key
        _.forIn(models, model => {
          _.forIn(model.attributes, attribute => {
            if (attribute.hasOwnProperty('via') && attribute.via === key) {
              if (attribute.hasOwnProperty('collection')) {
                types.other = 'collection';

                // Break loop
                return false;
              } else if (attribute.hasOwnProperty('model')) {
                types.other = 'modelD';

                // Break loop
                return false;
              }
            }
          });
        });
      } else if (association.hasOwnProperty('collection')) {
        types.current = 'collectionD';

        // We have to find if they are a model linked to this key
        _.forIn(models, model => {
          _.forIn(model.attributes, attribute => {
            if (attribute.hasOwnProperty('via') && attribute.via === key) {
              if (attribute.hasOwnProperty('collection')) {
                types.other = 'collection';

                // Break loop
                return false;
              } else if (attribute.hasOwnProperty('model')) {
                types.other = 'modelD';

                // Break loop
                return false;
              }
            }
          });
        });
      }

      if (types.current === 'collection' && types.other === 'morphTo') {
        return {
          nature: 'manyToManyMorph',
          verbose: 'morphMany'
        };
      } else if (types.current === 'collection' && types.other === 'morphToD') {
        return {
          nature: 'manyToOneMorph',
          verbose: 'morphMany'
        };
      }  else if (types.current === 'modelD' && types.other === 'morphTo') {
        return {
          nature: 'oneToManyMorph',
          verbose: 'morphOne'
        };
      } else if (types.current === 'modelD' && types.other === 'morphToD') {
        return {
          nature: 'oneToOneMorph',
          verbose: 'morphOne'
        };
      } else if (types.current === 'morphToD' && types.other === 'collection') {
        return {
          nature: 'oneMorphToMany',
          verbose: 'belongsToMorph'
        };
      } else if (types.current === 'morphToD' && types.other === 'model') {
        return {
          nature: 'oneMorphToOne',
          verbose: 'belongsToMorph'
        };
      } else if (types.current === 'morphTo' && (types.other === 'model' || association.hasOwnProperty('model'))) {
        return {
          nature: 'manyMorphToOne',
          verbose: 'belongsToManyMorph'
        };
      } else if (types.current === 'morphTo' && (types.other === 'collection' || association.hasOwnProperty('collection'))) {
        return {
          nature: 'manyMorphToMany',
          verbose: 'belongsToManyMorph'
        };
      } else if (types.current === 'modelD' && types.other === 'model') {
        return {
          nature: 'oneToOne',
          verbose: 'belongsTo'
        };
      } else if (types.current === 'model' && types.other === 'modelD') {
        return {
          nature: 'oneToOne',
          verbose: 'hasOne'
        };
      } else if ((types.current === 'model' || types.current === 'modelD') && types.other === 'collection') {
        return {
          nature: 'manyToOne',
          verbose: 'belongsTo'
        };
      } else if (types.current === 'modelD' && types.other === 'collection') {
        return {
          nature: 'oneToMany',
          verbose: 'hasMany'
        };
      } else if (types.current === 'collection' && types.other === 'model') {
        return {
          nature: 'oneToMany',
          verbose: 'hasMany'
        };
      } else if (types.current === 'collection' && types.other === 'collection') {
        return {
          nature: 'manyToMany',
          verbose: 'belongsToMany'
        };
      } else if (types.current === 'collectionD' && types.other === 'collection' || types.current === 'collection' && types.other === 'collectionD') {
        return {
          nature: 'manyToMany',
          verbose: 'belongsToMany'
        };
      } else if (types.current === 'collectionD' && types.other === '') {
        return {
          nature: 'manyWay',
          verbose: 'belongsToMany'
        };
      } else if (types.current === 'model' && types.other === '') {
        return {
          nature: 'oneWay',
          verbose: 'belongsTo'
        };
      }

      return undefined;
    } catch (e) {
      strapi.log.error(`Something went wrong in the model \`${_.upperFirst(currentModelName)}\` with the attribute \`${key}\``);
      strapi.log.error(e);
      strapi.stop();
    }
  },

  /**
   * Return ORM used for this collection.
   */

  getORM: collectionIdentity => {
    return _.get(strapi.models, collectionIdentity.toLowerCase() + '.orm');
  },

  /**
   * Define associations key to models
   */

  defineAssociations: function (model, definition, association, key) {
    try {
      // Initialize associations object
      if (definition.associations === undefined) {
        definition.associations = [];
      }

      // Exclude non-relational attribute
      if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model')) {
        return undefined;
      }

      // Get relation nature
      let details;
      const globalName = association.model || association.collection || '';
      const infos = this.getNature(association, key, undefined, model.toLowerCase());

      if (globalName !== '*') {
        details = association.plugin ?
          _.get(strapi.plugins, `${association.plugin}.models.${globalName}.attributes.${association.via}`, {}):
          _.get(strapi.models, `${globalName}.attributes.${association.via}`, {});
      }

      // Build associations object
      if (association.hasOwnProperty('collection') && association.collection !== '*') {
        definition.associations.push({
          alias: key,
          type: 'collection',
          collection: association.collection,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        });
      } else if (association.hasOwnProperty('model') && association.model !== '*') {
        definition.associations.push({
          alias: key,
          type: 'model',
          model: association.model,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        });
      } else if (association.hasOwnProperty('collection') || association.hasOwnProperty('model')) {
        const pluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
          Object.keys(strapi.plugins[current].models).forEach((entity) => {
            Object.keys(strapi.plugins[current].models[entity].attributes).forEach((attribute) => {
              const attr = strapi.plugins[current].models[entity].attributes[attribute];

              if (
                (attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() &&
                strapi.plugins[current].models[entity].globalId !== definition.globalId
              ) {
                acc.push(strapi.plugins[current].models[entity].globalId);
              }
            });
          });

          return acc;
        }, []);

        const appModels = Object.keys(strapi.models).reduce((acc, entity) => {
          Object.keys(strapi.models[entity].attributes).forEach((attribute) => {
            const attr = strapi.models[entity].attributes[attribute];

            if (
              (attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() &&
              strapi.models[entity].globalId !== definition.globalId
            ) {
              acc.push(strapi.models[entity].globalId);
            }
          });

          return acc;
        }, []);

        const models = _.uniq(appModels.concat(pluginsModels));

        definition.associations.push({
          alias: key,
          type: association.model ? 'model' : 'collection',
          related: models,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          filter: association.filter,
        });
      }
    } catch (e) {
      strapi.log.error(`Something went wrong in the model \`${_.upperFirst(model)}\` with the attribute \`${key}\``);
      strapi.log.error(e);
      strapi.stop();
    }
  },

  getVia: (attribute, association) => {
    return _.findKey(strapi.models[association.model || association.collection].attributes, {via: attribute});
  },

  convertParams: (entity, params) => {
    if (!entity) {
      throw new Error('You can\'t call the convert params method without passing the model\'s name as a first argument.');
    }

    // Remove the source params (that can be sent from the ctm plugin) since it is not a filter
    if (params.source) {
      delete params.source;
    }

    const model = entity.toLowerCase();

    const models = _.assign(_.clone(strapi.models), Object.keys(strapi.plugins).reduce((acc, current) => {
      _.assign(acc, _.get(strapi.plugins[current], ['models'], {}));
      return acc;
    }, {}));

    if (!models.hasOwnProperty(model)) {
      return this.log.error(`The model ${model} can't be found.`);
    }

    const client = models[model].client;
    const connector = models[model].orm;

    if (!connector) {
      throw new Error(`Impossible to determine the ORM used for the model ${model}.`);
    }

    const convertor = strapi.hook[connector].load().getQueryParams;
    const convertParams = {
      where: {},
      sort: '',
      start: 0,
      limit: 100
    };

    _.forEach(params, (value, key)  => {
      let result;
      let formattedValue;
      let modelAttributes = models[model]['attributes'];
      let fieldType;
      // Get the field type to later check if it's a string before number conversion
      if (modelAttributes[key]) {
        fieldType = modelAttributes[key]['type'];
      } else {
        // Remove the filter keyword at the end
        let splitKey = key.split('_').slice(0,-1);
        splitKey = splitKey.join('_');

        if (modelAttributes[splitKey]) {
          fieldType = modelAttributes[splitKey]['type'];
        }
      }
      // Check if the value is a valid candidate to be converted to a number value
      if (fieldType !== 'string') {
        formattedValue = isNumeric(value)
          ? _.toNumber(value)
          : value;
      } else {
        formattedValue = value;
      }

      if (_.includes(['_start', '_limit'], key)) {
        result = convertor(formattedValue, key);
      } else if (key === '_sort') {
        const [attr, order = 'ASC'] = formattedValue.split(':');
        result = convertor(order, key, attr);
      } else {
        const suffix = key.split('_');

        // Mysql stores boolean as 1 or 0
        if (client === 'mysql' && _.get(models, [model, 'attributes', suffix, 'type']) === 'boolean') {
          formattedValue = value === 'true' ? '1' : '0';
        }

        let type;

        if (_.includes(['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss', 'in'], _.last(suffix))) {
          type = `_${_.last(suffix)}`;
          key = _.dropRight(suffix).join('_');
        } else {
          type = '=';
        }

        result = convertor(formattedValue, type, key);
      }

      _.set(convertParams, result.key, result.value);
    });

    return convertParams;
  }
};
<commit_msg>Update isNumeric function with Object type check.<commit_after>'use strict';

/**
 * Module dependencies
 */

// Node.js core
const path = require('path');

// Public node modules.
const _ = require('lodash');

// Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate
const isNumeric = (value) => {
  return !_.isObject(value) && !isNaN(parseFloat(value)) && isFinite(value);
};

/* eslint-disable prefer-template */
/*
 * Set of utils for models
 */

module.exports = {

  /**
   * Initialize to prevent some mistakes
   */

  initialize: cb => {
    cb();
  },

  /**
   * Find primary key per ORM
   */

  getPK: function (collectionIdentity, collection, models) {
    if (_.isString(collectionIdentity)) {
      const ORM = this.getORM(collectionIdentity);

      try {
        const GraphQLFunctions = require(path.resolve(strapi.config.appPath, 'node_modules', 'strapi-' + ORM, 'lib', 'utils'));

        if (!_.isUndefined(GraphQLFunctions)) {
          return GraphQLFunctions.getPK(collectionIdentity, collection, models || strapi.models);
        }
      } catch (err) {
        return undefined;
      }
    }

    return undefined;
  },

  /**
   * Retrieve the value based on the primary key
   */

  getValuePrimaryKey: (value, defaultKey) => {
    return value[defaultKey] || value.id || value._id;
  },

  /**
   * Find primary key per ORM
   */

  getCount: function (collectionIdentity) {
    if (_.isString(collectionIdentity)) {
      const ORM = this.getORM(collectionIdentity);

      try {
        const ORMFunctions = require(path.resolve(strapi.config.appPath, 'node_modules', 'strapi-' + ORM, 'lib', 'utils'));

        if (!_.isUndefined(ORMFunctions)) {
          return ORMFunctions.getCount(collectionIdentity);
        }
      } catch (err) {
        return undefined;
      }
    }

    return undefined;
  },

  /**
   * Find relation nature with verbose
   */

  getNature: (association, key, models, currentModelName) => {
    try {
      const types = {
        current: '',
        other: ''
      };

      if (_.isUndefined(models)) {
        models = association.plugin ? strapi.plugins[association.plugin].models : strapi.models;
      }

      if ((association.hasOwnProperty('collection') && association.collection === '*') || (association.hasOwnProperty('model') && association.model === '*')) {
        if (association.model) {
          types.current = 'morphToD';
        } else {
          types.current = 'morphTo';
        }

        const flattenedPluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
          Object.keys(strapi.plugins[current].models).forEach((model) => {
            acc[`${current}_${model}`] = strapi.plugins[current].models[model];
          });

          return acc;
        }, {});

        const allModels = _.merge({}, strapi.models, flattenedPluginsModels);

        // We have to find if they are a model linked to this key
        _.forIn(allModels, model => {
          _.forIn(model.attributes, attribute => {
            if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.model === currentModelName) {
              if (attribute.hasOwnProperty('collection')) {
                types.other = 'collection';

                // Break loop
                return false;
              } else if (attribute.hasOwnProperty('model')) {
                types.other = 'model';

                // Break loop
                return false;
              }
            }
          });
        });
      } else if (association.hasOwnProperty('via') && association.hasOwnProperty('collection')) {
        const relatedAttribute = models[association.collection].attributes[association.via];

        if (!relatedAttribute) {
          throw new Error(`The attribute \`${association.via}\` is missing in the model ${_.upperFirst(association.collection)} ${association.plugin ? '(plugin - ' + association.plugin + ')' : '' }`);
        }

        types.current = 'collection';

        if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.collection !== '*' && relatedAttribute.hasOwnProperty('via')) {
          types.other = 'collection';
        } else if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.collection !== '*' && !relatedAttribute.hasOwnProperty('via')) {
          types.other = 'collectionD';
        } else if (relatedAttribute.hasOwnProperty('model') && relatedAttribute.model !== '*') {
          types.other = 'model';
        } else if (relatedAttribute.hasOwnProperty('collection') || relatedAttribute.hasOwnProperty('model')) {
          types.other = 'morphTo';
        }
      } else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) {
        types.current = 'modelD';

        // We have to find if they are a model linked to this key
        const model = models[association.model];
        const attribute = model.attributes[association.via];

        if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection') && attribute.collection !== '*') {
          types.other = 'collection';
        } else if (attribute.hasOwnProperty('model') && attribute.model !== '*') {
          types.other = 'model';
        } else if (attribute.hasOwnProperty('collection') || attribute.hasOwnProperty('model')) {
          types.other = 'morphTo';
        }
      } else if (association.hasOwnProperty('model')) {
        types.current = 'model';

        // We have to find if they are a model linked to this key
        _.forIn(models, model => {
          _.forIn(model.attributes, attribute => {
            if (attribute.hasOwnProperty('via') && attribute.via === key) {
              if (attribute.hasOwnProperty('collection')) {
                types.other = 'collection';

                // Break loop
                return false;
              } else if (attribute.hasOwnProperty('model')) {
                types.other = 'modelD';

                // Break loop
                return false;
              }
            }
          });
        });
      } else if (association.hasOwnProperty('collection')) {
        types.current = 'collectionD';

        // We have to find if they are a model linked to this key
        _.forIn(models, model => {
          _.forIn(model.attributes, attribute => {
            if (attribute.hasOwnProperty('via') && attribute.via === key) {
              if (attribute.hasOwnProperty('collection')) {
                types.other = 'collection';

                // Break loop
                return false;
              } else if (attribute.hasOwnProperty('model')) {
                types.other = 'modelD';

                // Break loop
                return false;
              }
            }
          });
        });
      }

      if (types.current === 'collection' && types.other === 'morphTo') {
        return {
          nature: 'manyToManyMorph',
          verbose: 'morphMany'
        };
      } else if (types.current === 'collection' && types.other === 'morphToD') {
        return {
          nature: 'manyToOneMorph',
          verbose: 'morphMany'
        };
      }  else if (types.current === 'modelD' && types.other === 'morphTo') {
        return {
          nature: 'oneToManyMorph',
          verbose: 'morphOne'
        };
      } else if (types.current === 'modelD' && types.other === 'morphToD') {
        return {
          nature: 'oneToOneMorph',
          verbose: 'morphOne'
        };
      } else if (types.current === 'morphToD' && types.other === 'collection') {
        return {
          nature: 'oneMorphToMany',
          verbose: 'belongsToMorph'
        };
      } else if (types.current === 'morphToD' && types.other === 'model') {
        return {
          nature: 'oneMorphToOne',
          verbose: 'belongsToMorph'
        };
      } else if (types.current === 'morphTo' && (types.other === 'model' || association.hasOwnProperty('model'))) {
        return {
          nature: 'manyMorphToOne',
          verbose: 'belongsToManyMorph'
        };
      } else if (types.current === 'morphTo' && (types.other === 'collection' || association.hasOwnProperty('collection'))) {
        return {
          nature: 'manyMorphToMany',
          verbose: 'belongsToManyMorph'
        };
      } else if (types.current === 'modelD' && types.other === 'model') {
        return {
          nature: 'oneToOne',
          verbose: 'belongsTo'
        };
      } else if (types.current === 'model' && types.other === 'modelD') {
        return {
          nature: 'oneToOne',
          verbose: 'hasOne'
        };
      } else if ((types.current === 'model' || types.current === 'modelD') && types.other === 'collection') {
        return {
          nature: 'manyToOne',
          verbose: 'belongsTo'
        };
      } else if (types.current === 'modelD' && types.other === 'collection') {
        return {
          nature: 'oneToMany',
          verbose: 'hasMany'
        };
      } else if (types.current === 'collection' && types.other === 'model') {
        return {
          nature: 'oneToMany',
          verbose: 'hasMany'
        };
      } else if (types.current === 'collection' && types.other === 'collection') {
        return {
          nature: 'manyToMany',
          verbose: 'belongsToMany'
        };
      } else if (types.current === 'collectionD' && types.other === 'collection' || types.current === 'collection' && types.other === 'collectionD') {
        return {
          nature: 'manyToMany',
          verbose: 'belongsToMany'
        };
      } else if (types.current === 'collectionD' && types.other === '') {
        return {
          nature: 'manyWay',
          verbose: 'belongsToMany'
        };
      } else if (types.current === 'model' && types.other === '') {
        return {
          nature: 'oneWay',
          verbose: 'belongsTo'
        };
      }

      return undefined;
    } catch (e) {
      strapi.log.error(`Something went wrong in the model \`${_.upperFirst(currentModelName)}\` with the attribute \`${key}\``);
      strapi.log.error(e);
      strapi.stop();
    }
  },

  /**
   * Return ORM used for this collection.
   */

  getORM: collectionIdentity => {
    return _.get(strapi.models, collectionIdentity.toLowerCase() + '.orm');
  },

  /**
   * Define associations key to models
   */

  defineAssociations: function (model, definition, association, key) {
    try {
      // Initialize associations object
      if (definition.associations === undefined) {
        definition.associations = [];
      }

      // Exclude non-relational attribute
      if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model')) {
        return undefined;
      }

      // Get relation nature
      let details;
      const globalName = association.model || association.collection || '';
      const infos = this.getNature(association, key, undefined, model.toLowerCase());

      if (globalName !== '*') {
        details = association.plugin ?
          _.get(strapi.plugins, `${association.plugin}.models.${globalName}.attributes.${association.via}`, {}):
          _.get(strapi.models, `${globalName}.attributes.${association.via}`, {});
      }

      // Build associations object
      if (association.hasOwnProperty('collection') && association.collection !== '*') {
        definition.associations.push({
          alias: key,
          type: 'collection',
          collection: association.collection,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        });
      } else if (association.hasOwnProperty('model') && association.model !== '*') {
        definition.associations.push({
          alias: key,
          type: 'model',
          model: association.model,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        });
      } else if (association.hasOwnProperty('collection') || association.hasOwnProperty('model')) {
        const pluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
          Object.keys(strapi.plugins[current].models).forEach((entity) => {
            Object.keys(strapi.plugins[current].models[entity].attributes).forEach((attribute) => {
              const attr = strapi.plugins[current].models[entity].attributes[attribute];

              if (
                (attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() &&
                strapi.plugins[current].models[entity].globalId !== definition.globalId
              ) {
                acc.push(strapi.plugins[current].models[entity].globalId);
              }
            });
          });

          return acc;
        }, []);

        const appModels = Object.keys(strapi.models).reduce((acc, entity) => {
          Object.keys(strapi.models[entity].attributes).forEach((attribute) => {
            const attr = strapi.models[entity].attributes[attribute];

            if (
              (attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() &&
              strapi.models[entity].globalId !== definition.globalId
            ) {
              acc.push(strapi.models[entity].globalId);
            }
          });

          return acc;
        }, []);

        const models = _.uniq(appModels.concat(pluginsModels));

        definition.associations.push({
          alias: key,
          type: association.model ? 'model' : 'collection',
          related: models,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          filter: association.filter,
        });
      }
    } catch (e) {
      strapi.log.error(`Something went wrong in the model \`${_.upperFirst(model)}\` with the attribute \`${key}\``);
      strapi.log.error(e);
      strapi.stop();
    }
  },

  getVia: (attribute, association) => {
    return _.findKey(strapi.models[association.model || association.collection].attributes, {via: attribute});
  },

  convertParams: (entity, params) => {
    if (!entity) {
      throw new Error('You can\'t call the convert params method without passing the model\'s name as a first argument.');
    }

    // Remove the source params (that can be sent from the ctm plugin) since it is not a filter
    if (params.source) {
      delete params.source;
    }

    const model = entity.toLowerCase();

    const models = _.assign(_.clone(strapi.models), Object.keys(strapi.plugins).reduce((acc, current) => {
      _.assign(acc, _.get(strapi.plugins[current], ['models'], {}));
      return acc;
    }, {}));

    if (!models.hasOwnProperty(model)) {
      return this.log.error(`The model ${model} can't be found.`);
    }

    const client = models[model].client;
    const connector = models[model].orm;

    if (!connector) {
      throw new Error(`Impossible to determine the ORM used for the model ${model}.`);
    }

    const convertor = strapi.hook[connector].load().getQueryParams;
    const convertParams = {
      where: {},
      sort: '',
      start: 0,
      limit: 100
    };

    _.forEach(params, (value, key)  => {
      let result;
      let formattedValue;
      let modelAttributes = models[model]['attributes'];
      let fieldType;
      // Get the field type to later check if it's a string before number conversion
      if (modelAttributes[key]) {
        fieldType = modelAttributes[key]['type'];
      } else {
        // Remove the filter keyword at the end
        let splitKey = key.split('_').slice(0,-1);
        splitKey = splitKey.join('_');

        if (modelAttributes[splitKey]) {
          fieldType = modelAttributes[splitKey]['type'];
        }
      }
      // Check if the value is a valid candidate to be converted to a number value
      if (fieldType !== 'string') {
        formattedValue = isNumeric(value)
          ? _.toNumber(value)
          : value;
      } else {
        formattedValue = value;
      }

      if (_.includes(['_start', '_limit'], key)) {
        result = convertor(formattedValue, key);
      } else if (key === '_sort') {
        const [attr, order = 'ASC'] = formattedValue.split(':');
        result = convertor(order, key, attr);
      } else {
        const suffix = key.split('_');

        // Mysql stores boolean as 1 or 0
        if (client === 'mysql' && _.get(models, [model, 'attributes', suffix, 'type']) === 'boolean') {
          formattedValue = value === 'true' ? '1' : '0';
        }

        let type;

        if (_.includes(['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss', 'in'], _.last(suffix))) {
          type = `_${_.last(suffix)}`;
          key = _.dropRight(suffix).join('_');
        } else {
          type = '=';
        }

        result = convertor(formattedValue, type, key);
      }

      _.set(convertParams, result.key, result.value);
    });

    return convertParams;
  }
};
<|endoftext|>


<commit_before><commit_msg>improving for IE<commit_after><|endoftext|>


<commit_before>html_scanner = {
  // Scan a template file for <head>, <body>, and <template>
  // tags and extract their contents.
  //
  // This is a primitive, regex-based scanner.  It scans
  // top-level tags, which are allowed to have attributes,
  // and ignores top-level HTML comments.

  scan: function (contents, source_name) {
    var rest = contents;
    var index = 0;

    var advance = function(amount) {
      rest = rest.substring(amount);
      index += amount;
    };

    var parseError = function(msg) {
      var lineNumber = contents.substring(0, index).split('\n').length;
      var line = contents.split('\n')[lineNumber - 1];
      var info = "line "+lineNumber+", file "+source_name + "\n" + line;
      return new Error((msg || "Parse error")+" - "+info);
    };

    var results = html_scanner._initResults();

    var rOpenTag = /^((<(template|head|body)\b)|(<!--)|(<!DOCTYPE|{{!)|$)/i;

    while (rest) {
      // skip whitespace first (for better line numbers)
      advance(rest.match(/^\s*/)[0].length);

      var match = rOpenTag.exec(rest);
      if (! match)
        throw parseError(); // unknown text encountered

      var matchToken = match[1];
      var matchTokenTagName =  match[3];
      var matchTokenComment = match[4];
      var matchTokenUnsupported = match[5];

      advance(match.index + match[0].length);

      if (! matchToken)
        break; // matched $ (end of file)
      if (matchTokenComment === '<!--') {
        // top-level HTML comment
        var commentEnd = /--\s*>/.exec(rest);
        if (! commentEnd)
          throw parseError("unclosed HTML comment");
        advance(commentEnd.index + commentEnd[0].length);
        continue;
      }
      if (matchTokenUnsupported) {
        switch (matchTokenUnsupported.toLowerCase()) {
        case '<!doctype':
          throw parseError(
            "Can't set DOCTYPE here.  (Meteor sets <!DOCTYPE html> for you)");
        case '{{!':
          throw new parseError(
            "Can't use '{{! }}' outside a template.  Use '<!-- -->'.");
        }
        throw new parseError();
      }

      // otherwise, a <tag>
      var tagName = matchTokenTagName.toLowerCase();
      var tagAttribs = {}; // bare name -> value dict
      var rTagPart = /^\s*((([a-zA-Z0-9:_-]+)\s*=\s*(["'])(.*?)\4)|(>))/;
      var attr;
      // read attributes
      while ((attr = rTagPart.exec(rest))) {
        var attrToken = attr[1];
        var attrKey = attr[3];
        var attrValue = attr[5];
        advance(attr.index + attr[0].length);
        if (attrToken === '>')
          break;
        // XXX we don't HTML unescape the attribute value
        // (e.g. to allow "abcd"efg") or protect against
        // collisions with methods of tagAttribs (e.g. for
        // a property named toString)
        attrValue = attrValue.match(/^\s*([\s\S]*?)\s*$/)[1]; // trim
        tagAttribs[attrKey] = attrValue;
      }
      if (! attr) // didn't end on '>'
        throw new parseError("Parse error in tag");
      // find </tag>
      var end = (new RegExp('</'+tagName+'\\s*>', 'i')).exec(rest);
      if (! end)
        throw new parseError("unclosed <"+tagName+">");
      var tagContents = rest.slice(0, end.index);
      advance(end.index + end[0].length);

      // act on the tag
      html_scanner._handleTag(results, tagName, tagAttribs, tagContents,
                              parseError);
    }

    return results;
  },

  _initResults: function() {
    var results = {};
    results.head = '';
    results.body = '';
    results.js = '';
    return results;
  },

  _handleTag: function (results, tag, attribs, contents, parseError) {

    // trim the tag contents.
    // this is a courtesy and is also relied on by some unit tests.
    contents = contents.match(/^[ \t\r\n]*([\s\S]*?)[ \t\r\n]*$/)[1];

    // do we have 1 or more attribs?
    var hasAttribs = false;
    for(var k in attribs) {
      if (attribs.hasOwnProperty(k)) {
        hasAttribs = true;
        break;
      }
    }

    if (tag === "head") {
      if (hasAttribs)
        throw parseError("Attributes on <head> not supported");
      results.head += contents;
      return;
    }

    // <body> or <template>
    var code = 'Package.templating.Handlebars.json_ast_to_func(' +
          JSON.stringify(Handlebars.to_json_ast(contents)) + ')';

    if (tag === "template") {
      var name = attribs.name;
      if (! name)
        throw parseError("Template has no 'name' attribute");

      results.js += "Meteor._def_template(" + JSON.stringify(name) + ","
        + code + ");\n";
    } else {
      // <body>
      if (hasAttribs)
        throw parseError("Attributes on <body> not supported");
      results.js += "Meteor.startup(function(){" +
        "document.body.appendChild(Spark.render(" +
        "Meteor._def_template(null," + code + ")));});";
    }
  }
};
<commit_msg>remove apparently-defunct copy of html_scanner<commit_after><|endoftext|>


<commit_before>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

const fs = require('fs');
const sass = require('sass');
const path = require('path');

const SassRenderer = {
  create(cwd, initialData = '') {
    const nodeModules = getNodeModulesFolders(cwd);

    async function render(data) {
      const values = [];
      const valuesByKey = new Map();
      const result = sass.renderSync({
        data: `${initialData}\n${data}`,
        functions: {
          'get-value($arg)': (arg) => {
            values.push(arg);
            return sass.types.Null.NULL;
          },
          'get($key, $value)': (key, value) => {
            valuesByKey.set(convert(key), {
              value: convert(value),
              nativeValue: value,
            });
            return sass.types.Null.NULL;
          },
        },
        includePaths: [cwd, ...nodeModules],
      });

      return {
        result,
        values,
        getValue(index) {
          return convert(values[index]);
        },
        get(key) {
          if (valuesByKey.has(key)) {
            return valuesByKey.get(key);
          }
          throw new Error(`Unabled to find value with key: ${key}`);
        },
        unwrap(key) {
          if (valuesByKey.has(key)) {
            return valuesByKey.get(key).value;
          }
          throw new Error(`Unabled to find value with key: ${key}`);
        },
      };
    }

    return {
      convert,
      render,
      types: sass.types,
    };
  },
};

/**
 * Converts a value from Sass into a comparable JavaScript type
 */
function convert(value) {
  const { types } = sass;

  if (value instanceof types.Boolean) {
    return value.getValue();
  }

  if (value instanceof types.Number) {
    const unit = value.getUnit();
    if (unit === '') {
      return value.getValue();
    }
    return `${value.getValue()}${unit}`;
  }

  if (value instanceof types.String) {
    return value.getValue();
  }

  if (value instanceof types.Color) {
    return value.toString();
  }

  if (value instanceof types.List) {
    const length = value.getLength();
    const result = Array(length);

    for (let i = 0; i < length; i++) {
      result[i] = convert(value.getValue(i));
    }

    return result;
  }

  if (value instanceof types.Map) {
    const length = value.getLength();
    const result = {};

    for (let i = 0; i < length; i++) {
      const key = convert(value.getKey(i));
      result[key] = convert(value.getValue(i));
    }

    return result;
  }

  if (value instanceof types.Null) {
    return null;
  }

  return value;
}

/**
 * Collect all the node_modules folders that are present in the current path by
 * traversing upwards and looking for if a node_modules folder exists. This is
 * useful for the `includePaths` option for a sass renderer
 * @param {string} cwd
 * @returns {Array<string>}
 */
function getNodeModulesFolders(cwd) {
  const { root } = path.parse(cwd);
  const folders = [];
  let directory = cwd;

  while (directory !== root) {
    const folder = path.join(directory, 'node_modules');
    if (fs.existsSync(folder)) {
      folders.push(folder);
    }
    directory = path.dirname(directory);
  }

  return folders;
}

module.exports = {
  SassRenderer,
};
<commit_msg>chore(project): add quietDeps for sass tests (#9434)<commit_after>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

const fs = require('fs');
const sass = require('sass');
const path = require('path');

const SassRenderer = {
  create(cwd, initialData = '') {
    const nodeModules = getNodeModulesFolders(cwd);

    async function render(data) {
      const values = [];
      const valuesByKey = new Map();
      const result = sass.renderSync({
        data: `${initialData}\n${data}`,
        functions: {
          'get-value($arg)': (arg) => {
            values.push(arg);
            return sass.types.Null.NULL;
          },
          'get($key, $value)': (key, value) => {
            valuesByKey.set(convert(key), {
              value: convert(value),
              nativeValue: value,
            });
            return sass.types.Null.NULL;
          },
        },
        includePaths: [cwd, ...nodeModules],
        quietDeps: true,
      });

      return {
        result,
        values,
        getValue(index) {
          return convert(values[index]);
        },
        get(key) {
          if (valuesByKey.has(key)) {
            return valuesByKey.get(key);
          }
          throw new Error(`Unabled to find value with key: ${key}`);
        },
        unwrap(key) {
          if (valuesByKey.has(key)) {
            return valuesByKey.get(key).value;
          }
          throw new Error(`Unabled to find value with key: ${key}`);
        },
      };
    }

    return {
      convert,
      render,
      types: sass.types,
    };
  },
};

/**
 * Converts a value from Sass into a comparable JavaScript type
 */
function convert(value) {
  const { types } = sass;

  if (value instanceof types.Boolean) {
    return value.getValue();
  }

  if (value instanceof types.Number) {
    const unit = value.getUnit();
    if (unit === '') {
      return value.getValue();
    }
    return `${value.getValue()}${unit}`;
  }

  if (value instanceof types.String) {
    return value.getValue();
  }

  if (value instanceof types.Color) {
    return value.toString();
  }

  if (value instanceof types.List) {
    const length = value.getLength();
    const result = Array(length);

    for (let i = 0; i < length; i++) {
      result[i] = convert(value.getValue(i));
    }

    return result;
  }

  if (value instanceof types.Map) {
    const length = value.getLength();
    const result = {};

    for (let i = 0; i < length; i++) {
      const key = convert(value.getKey(i));
      result[key] = convert(value.getValue(i));
    }

    return result;
  }

  if (value instanceof types.Null) {
    return null;
  }

  return value;
}

/**
 * Collect all the node_modules folders that are present in the current path by
 * traversing upwards and looking for if a node_modules folder exists. This is
 * useful for the `includePaths` option for a sass renderer
 * @param {string} cwd
 * @returns {Array<string>}
 */
function getNodeModulesFolders(cwd) {
  const { root } = path.parse(cwd);
  const folders = [];
  let directory = cwd;

  while (directory !== root) {
    const folder = path.join(directory, 'node_modules');
    if (fs.existsSync(folder)) {
      folders.push(folder);
    }
    directory = path.dirname(directory);
  }

  return folders;
}

module.exports = {
  SassRenderer,
};
<|endoftext|>


<commit_before>/*
This module is all about hover nodes. Want a hover node?
Well, use this module.
*/

CT.hover = {
	_: {},
	_pos: function(n, recursive, auto) {
		if (auto) {
			var rect = n.getBoundingClientRect();
			return [rect.left, rect.top];
		}
		if (recursive) {
			var left = 0, top = 0;
			while (!isNaN(n.offsetLeft) && n != CT.dom.ALLNODE) {
				left += n.offsetLeft;
				top += n.offsetTop;
				n = n.parentNode;
			}
			return [left, top];
		}
		return [n.offsetLeft, n.offsetTop];
	},
	_hide: function() {
		var _ = CT.hover._;
		setTimeout(function() {
			!_.nodeOn && !_.hoverOn && CT.dom.hide(_.infoBubble);
		}, 1000);
	},
	set: function(node, content, poptop, stayopen, recursive, auto) {
		if (arguments.length == 1 && !(node instanceof Node)) {
			var obj = arguments[0];
			node = obj.node;
			content = obj.content;
			poptop = obj.poptop;
			stayopen = obj.stayopen;
			recursive = obj.recursive;
			auto = obj.auto;
		}
		var _ = CT.hover._;
		if (!_.infoBubble) {
			_.infoBubble = CT.dom.div("", "small hidden infobubble");
			CT.dom.getAllNode(true).appendChild(_.infoBubble);
			_.bubbleBounds = {
				'left': 0,
				'top': 0
			};
		}
		node.onmouseover = function(e) {
			_.nodeOn = true;
			var npos = CT.hover._pos(node, recursive, auto); // recheck every time in case target moves
			CT.dom.ALLNODE.appendChild(_.infoBubble);
			CT.dom.setContent(_.infoBubble, content);
			CT.dom.show(_.infoBubble);
			var voffset = poptop ? -(_.infoBubble.clientHeight + 10)
				: ((node.clientHeight || node.offsetHeight) + 10);
			_.bubbleBounds.right = CT.dom.ALLNODE.clientWidth - _.infoBubble.clientWidth;
			_.bubbleBounds.bottom = CT.dom.ALLNODE.clientHeight - _.infoBubble.clientHeight;
			_.infoBubble.style.left = Math.min(_.bubbleBounds.right - 10,
				Math.max(10, npos[0] - (_.infoBubble.clientWidth - (node.clientWidth || node.offsetWidth)) / 2)) + "px";
			_.infoBubble.style.top = (npos[1] + voffset) + "px";
		};
		node.onmouseout = function() {
			_.nodeOn = false;
			CT.hover._hide();
		};
		if (stayopen) {
			content.onmouseover = function(e) {
				_.hoverOn = true;
			};
			content.onmouseout = function(e) {
				_.hoverOn = false;
				CT.hover._hide();
			};
		}
		return node;
	}
};<commit_msg>CT.hover.set(): onmouseover(), if auto, stopPropagation()<commit_after>/*
This module is all about hover nodes. Want a hover node?
Well, use this module.
*/

CT.hover = {
	_: {},
	_pos: function(n, recursive, auto) {
		if (auto) {
			var rect = n.getBoundingClientRect();
			return [rect.left, rect.top];
		}
		if (recursive) {
			var left = 0, top = 0;
			while (!isNaN(n.offsetLeft) && n != CT.dom.ALLNODE) {
				left += n.offsetLeft;
				top += n.offsetTop;
				n = n.parentNode;
			}
			return [left, top];
		}
		return [n.offsetLeft, n.offsetTop];
	},
	_hide: function() {
		var _ = CT.hover._;
		setTimeout(function() {
			!_.nodeOn && !_.hoverOn && CT.dom.hide(_.infoBubble);
		}, 1000);
	},
	set: function(node, content, poptop, stayopen, recursive, auto) {
		if (arguments.length == 1 && !(node instanceof Node)) {
			var obj = arguments[0];
			node = obj.node;
			content = obj.content;
			poptop = obj.poptop;
			stayopen = obj.stayopen;
			recursive = obj.recursive;
			auto = obj.auto;
		}
		var _ = CT.hover._;
		if (!_.infoBubble) {
			_.infoBubble = CT.dom.div("", "small hidden infobubble");
			CT.dom.getAllNode(true).appendChild(_.infoBubble);
			_.bubbleBounds = {
				'left': 0,
				'top': 0
			};
		}
		node.onmouseover = function(e) {
			_.nodeOn = true;
			var npos = CT.hover._pos(node, recursive, auto); // recheck every time in case target moves
			CT.dom.ALLNODE.appendChild(_.infoBubble);
			CT.dom.setContent(_.infoBubble, content);
			CT.dom.show(_.infoBubble);
			var voffset = poptop ? -(_.infoBubble.clientHeight + 10)
				: ((node.clientHeight || node.offsetHeight) + 10);
			_.bubbleBounds.right = CT.dom.ALLNODE.clientWidth - _.infoBubble.clientWidth;
			_.bubbleBounds.bottom = CT.dom.ALLNODE.clientHeight - _.infoBubble.clientHeight;
			_.infoBubble.style.left = Math.min(_.bubbleBounds.right - 10,
				Math.max(10, npos[0] - (_.infoBubble.clientWidth - (node.clientWidth || node.offsetWidth)) / 2)) + "px";
			_.infoBubble.style.top = (npos[1] + voffset) + "px";
			auto && e.stopPropagation();
		};
		node.onmouseout = function() {
			_.nodeOn = false;
			CT.hover._hide();
		};
		if (stayopen) {
			content.onmouseover = function(e) {
				_.hoverOn = true;
			};
			content.onmouseout = function(e) {
				_.hoverOn = false;
				CT.hover._hide();
			};
		}
		return node;
	}
};<|endoftext|>


<commit_before>/*
This module provides convenience functions for messing
around with DOM elements via CSS transitions. Have at it.

### Try out these functions:
	CT.trans.rotate(node, opts)
	CT.trans.translate(node, opts)
	CT.trans.wobble(node, opts)
	CT.trans.pan(node, opts, wait)
	CT.trans.resize(node, opts)
	CT.trans.fadeIn(node, opts)
	CT.trans.fadeOut(node, opts)
	CT.trans.pulse(node, opts)
	CT.trans.trans(opts)
	CT.trans.setVendorPrefixed(node, property, value)
	 - sets CSS properties for all vendor prefixes
	   - [ "-webkit-", "-moz-", "-ms-", "-o-", "" ]

### And here are the default options:
	trans: {
		duration: 500,
		property: "*",
		ease: "ease-in-out"
	},
	rotate: {
		degrees: 180,
		duration: 1000,
		property: "transform",
		ease: "linear",
		prefix: true
	},
	translate: {
		duration: 300,
		property: "transform",
		ease: "linear",
		prefix: true,
		x: 0,
		y: 0,
		z: 0
	},
	wobble: {
		axis: "x",
		radius: 50,
		duration: 100
	},
	pan: {
		duration: 5000,
		ease: "linear",
		x: 0,
		y: 0
	},
	resize: {
		duration: 300,
		ease: "linear"
	},
	fadeIn: {
		duration: 1600,
		property: "opacity",
		value: 1
	},
	fadeOut: {
		duration: 1600,
		property: "opacity",
		value: 0
	},
	pulse: {
		wait: 1000
	},
	invert: {
		direction: "horizontal", // or vertical
		property: "transform",
		prefix: true
	}

TODO: let's add some more, like scale.

Certain functions (pan() and pulse()) return a CT.trans.Controller instance.

### CT.trans.Controller
	pause()  - stop the transition (for now)
	resume() - resume the transition
	active() - returns status of transition (bool)
	tick()   - calls cb() if active()
	init(cb) - cb() is called by tick() if active()
*/

CT.trans = {
	_: {
		vender_prefixes: [ "-webkit-", "-moz-", "-ms-", "-o-", "" ],
		enames: [ "webkitTransitionEnd", "mozTransitionEnd", "oTransitionEnd", "transitionend"],
		defaults: {
			trans: {
				duration: 500,
				property: "*",
				ease: "ease-in-out"
			},
			rotate: {
				degrees: 180,
				duration: 1000,
				property: "transform",
				ease: "linear",
				prefix: true
			},
			translate: {
				duration: 300,
				property: "transform",
				ease: "linear",
				prefix: true,
				x: 0,
				y: 0,
				z: 0
			},
			wobble: {
				axis: "x",
				radius: 50,
				duration: 100
			},
			pan: {
				duration: 5000,
				ease: "linear",
				x: 0,
				y: 0
			},
			resize: {
				duration: 300,
				ease: "linear"
			},
			fadeIn: {
				duration: 1600,
				property: "opacity",
				value: 1
			},
			fadeOut: {
				duration: 1600,
				property: "opacity",
				value: 0
			},
			pulse: {
				wait: 1000
			},
			invert: {
				direction: "horizontal", // or vertical
				property: "transform",
				prefix: true
			}
		}
	},
	setVenderPrefixed: function(node, property, value) {
	    for (var i = 0; i < CT.trans._.vender_prefixes.length; i++)
	        node.style[CT.trans._.vender_prefixes[i] + property] = value;
	},
	trans: function(opts) {
		// opts: node, property, value, cb, duration, ease, prefix
		opts = CT.merge(opts, CT.trans._.defaults.trans);
		if (typeof opts.node == "string")
			opts.node = CT.dom.id(opts.node);
		if (opts.node) {
			var prevTrans = opts.node.style.transition || "",
				transComponent = opts.property + " " + opts.duration + "ms " + opts.ease,
		        fullTrans = (prevTrans && prevTrans.indexOf(transComponent) == -1)
		        	? (prevTrans + ", " + transComponent) : transComponent;
		    CT.trans.setVenderPrefixed(opts.node, "transition", fullTrans);
		}
	    if (opts.cb) {
	        var transTimeout, wrapper = function () {
	            opts.node && CT.trans.setVenderPrefixed(opts.node, "transition", prevTrans);
	            clearTimeout(transTimeout);
	            transTimeout = null;
	            if (opts.node) CT.trans._.enames.forEach(function(ename) {
		            opts.node.removeEventListener(ename, wrapper, false);
            	});
	            opts.cb();
	        }
            if (opts.node) CT.trans._.enames.forEach(function(ename) {
	            opts.node.addEventListener(ename, wrapper, false);
        	});
	        transTimeout = setTimeout(wrapper, opts.duration);
	    }
	    if (opts.node && opts.property && opts.property != "*") {
	        if (opts.prefix)
	            CT.trans.setVenderPrefixed(opts.node, opts.property, opts.value);
	        else
	            opts.node.style[opts.property] = opts.value;
	    }
	},
	invert: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.invert);
		opts.node = node;
		opts.value = "scale" + (opts.direction == "horizontal"
			? "X" : "Y") + "(-1)";
		CT.trans.trans(opts);
		return node;
	},
	rotate: function(node, opts) {
		// opts may include: duration, degrees, cb, ease, forever
		opts = CT.merge(opts, CT.trans._.defaults.rotate);
		opts.node = node;
		opts.value = "rotate(" + opts.degrees + "deg)";
		if (opts.forever) opts.cb = function() {
			opts.degrees += CT.trans._.defaults.rotate.degrees;
			CT.trans.rotate(node, opts);
		};
		CT.trans.trans(opts);
		return node;
	},
	translate: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.translate);
		opts.node = node;
		if (opts.axis)
			opts[opts.axis] = opts.value;
		opts.value = "translate3d(" + opts.x + "px," + opts.y + "px," + opts.z + "px)",
		CT.trans.trans(opts);
	},
	wobble: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.wobble);
		CT.trans.translate(node, {
			axis: opts.axis,
			ease: "ease-out",
			value: opts.radius,
			duration: opts.duration,
			cb: function() {
				CT.trans.translate(node, {
					axis: opts.axis,
					ease: "ease-in-out",
					value: -opts.radius,
					duration: opts.duration * 2,
					cb: function() {
						CT.trans.translate(node, {
							axis: opts.axis,
							ease: "ease-in",
							value: 0,
							duration: opts.duration
						});
					}
				});
			}
		});
	},
	pan: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.pan);
		var setV = function(cb) {
			node._vertical = CT.dom.cover(node);
			cb && setTimeout(cb);
		}, controllercb = function() {
			if (node._vertical == undefined)
				return setV(controllercb);
			var prop = node._vertical ? "y" : "x",
				dim = node._vertical ? "clientHeight" : "clientWidth";
			if (opts[prop])
				opts[prop] = 0;
			else
				opts[prop] = node.parentNode[dim] - node[dim];
			CT.trans.translate(node, opts);
		}, controller = new CT.trans.Controller(controllercb);
		node.onload = function() {
			setTimeout(controller.resume); // wait a tic!
		};
		CT.onresize(node.onload);
		opts.cb = controller.tick;
		return controller;
	},
	resize: function(node, opts) {
		var width = opts.width,
			height = opts.height,
			cb = opts.cb;
		delete opts.width;
		delete opts.height;
		delete opts.cb;
		opts.node = node;
		opts = CT.merge(opts, CT.trans._.defaults.resize);
		if (width) {
			CT.trans.trans(CT.merge({
				property: "width",
				value: width
			}, opts));
		}
		if (height) {
			CT.trans.trans(CT.merge({
				property: "height",
				value: height
			}, opts));
		}
		if (cb) {
			CT.trans.trans(CT.merge({
				cb: cb
			}, opts));
		}
	},
	fadeIn: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.fadeIn);
		opts.node = node;
		CT.trans.trans(opts);
	},
	fadeOut: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.fadeOut);
		opts.node = node;
		CT.trans.trans(opts);
	},
	pulse: function(node, opts, wait) {
		opts = CT.merge(opts, CT.trans._.defaults.pulse);
		var faded, controller = new CT.trans.Controller(function() {
			faded = !faded;
			if (faded) {
				setTimeout(function() {
					CT.trans.fadeIn(node, opts);
				}, opts.wait);
			} else
				CT.trans.fadeOut(node, opts);
		});
		opts.cb = controller.tick;
		if (!wait)
			controller.tick();
		return controller;
	}
};

CT.trans.Controller = CT.Class({
	CLASSNAME: "CT.trans.Controller",
	pause: function() {
		this._paused = true;
	},
	resume: function() {
		if (this._paused != false) {
			this._paused = false;
			this.cb();
		}
	},
	active: function() {
		return !this._paused;
	},
	tick: function() {
		this.active() && this.cb();
	},
	init: function(cb) {
		this.cb = cb;
	}
});<commit_msg>CT.trans: trans() only sets timeout if no node is present (for conventional event listeners); pan() sets _prop, _dim, and _bound in setV() (instead of per tick), simplifying position swapping; Controller incorporates _stalled concept to avoid ticking (via resume()) before a previous tick completes.<commit_after>/*
This module provides convenience functions for messing
around with DOM elements via CSS transitions. Have at it.

### Try out these functions:
	CT.trans.rotate(node, opts)
	CT.trans.translate(node, opts)
	CT.trans.wobble(node, opts)
	CT.trans.pan(node, opts, wait)
	CT.trans.resize(node, opts)
	CT.trans.fadeIn(node, opts)
	CT.trans.fadeOut(node, opts)
	CT.trans.pulse(node, opts)
	CT.trans.trans(opts)
	CT.trans.setVendorPrefixed(node, property, value)
	 - sets CSS properties for all vendor prefixes
	   - [ "-webkit-", "-moz-", "-ms-", "-o-", "" ]

### And here are the default options:
	trans: {
		duration: 500,
		property: "*",
		ease: "ease-in-out"
	},
	rotate: {
		degrees: 180,
		duration: 1000,
		property: "transform",
		ease: "linear",
		prefix: true
	},
	translate: {
		duration: 300,
		property: "transform",
		ease: "linear",
		prefix: true,
		x: 0,
		y: 0,
		z: 0
	},
	wobble: {
		axis: "x",
		radius: 50,
		duration: 100
	},
	pan: {
		duration: 5000,
		ease: "linear",
		x: 0,
		y: 0
	},
	resize: {
		duration: 300,
		ease: "linear"
	},
	fadeIn: {
		duration: 1600,
		property: "opacity",
		value: 1
	},
	fadeOut: {
		duration: 1600,
		property: "opacity",
		value: 0
	},
	pulse: {
		wait: 1000
	},
	invert: {
		direction: "horizontal", // or vertical
		property: "transform",
		prefix: true
	}

TODO: let's add some more, like scale.

Certain functions (pan() and pulse()) return a CT.trans.Controller instance.

### CT.trans.Controller
	pause()  - stop the transition (for now)
	resume() - resume the transition
	active() - returns status of transition (bool)
	tick()   - calls cb() if active()
	init(cb) - cb() is called by tick() if active()
*/

CT.trans = {
	_: {
		vender_prefixes: [ "-webkit-", "-moz-", "-ms-", "-o-", "" ],
		enames: [ "webkitTransitionEnd", "mozTransitionEnd", "oTransitionEnd", "transitionend"],
		defaults: {
			trans: {
				duration: 500,
				property: "*",
				ease: "ease-in-out"
			},
			rotate: {
				degrees: 180,
				duration: 1000,
				property: "transform",
				ease: "linear",
				prefix: true
			},
			translate: {
				duration: 300,
				property: "transform",
				ease: "linear",
				prefix: true,
				x: 0,
				y: 0,
				z: 0
			},
			wobble: {
				axis: "x",
				radius: 50,
				duration: 100
			},
			pan: {
				duration: 5000,
				ease: "linear",
				x: 0,
				y: 0
			},
			resize: {
				duration: 300,
				ease: "linear"
			},
			fadeIn: {
				duration: 1600,
				property: "opacity",
				value: 1
			},
			fadeOut: {
				duration: 1600,
				property: "opacity",
				value: 0
			},
			pulse: {
				wait: 1000
			},
			invert: {
				direction: "horizontal", // or vertical
				property: "transform",
				prefix: true
			}
		}
	},
	setVenderPrefixed: function(node, property, value) {
	    for (var i = 0; i < CT.trans._.vender_prefixes.length; i++)
	        node.style[CT.trans._.vender_prefixes[i] + property] = value;
	},
	trans: function(opts) {
		// opts: node, property, value, cb, duration, ease, prefix
		opts = CT.merge(opts, CT.trans._.defaults.trans);
		if (typeof opts.node == "string")
			opts.node = CT.dom.id(opts.node);
		if (opts.node) {
			var prevTrans = opts.node.style.transition || "",
				transComponent = opts.property + " " + opts.duration + "ms " + opts.ease,
		        fullTrans = (prevTrans && prevTrans.indexOf(transComponent) == -1)
		        	? (prevTrans + ", " + transComponent) : transComponent;
		    CT.trans.setVenderPrefixed(opts.node, "transition", fullTrans);
		}
	    if (opts.cb) {
	        var transTimeout, wrapper = function () {
	            opts.node && CT.trans.setVenderPrefixed(opts.node, "transition", prevTrans);
	            clearTimeout(transTimeout);
	            transTimeout = null;
	            if (opts.node) CT.trans._.enames.forEach(function(ename) {
		            opts.node.removeEventListener(ename, wrapper, false);
            	});
	            opts.cb();
	        }
            if (opts.node)
            	CT.trans._.enames.forEach(function(ename) {
		            opts.node.addEventListener(ename, wrapper, false);
	        	});
            else // only use timeout as fallback
		        transTimeout = setTimeout(wrapper, opts.duration);
	    }
	    if (opts.node && opts.property && opts.property != "*") {
	        if (opts.prefix)
	            CT.trans.setVenderPrefixed(opts.node, opts.property, opts.value);
	        else
	            opts.node.style[opts.property] = opts.value;
	    }
	},
	invert: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.invert);
		opts.node = node;
		opts.value = "scale" + (opts.direction == "horizontal"
			? "X" : "Y") + "(-1)";
		CT.trans.trans(opts);
		return node;
	},
	rotate: function(node, opts) {
		// opts may include: duration, degrees, cb, ease, forever
		opts = CT.merge(opts, CT.trans._.defaults.rotate);
		opts.node = node;
		opts.value = "rotate(" + opts.degrees + "deg)";
		if (opts.forever) opts.cb = function() {
			opts.degrees += CT.trans._.defaults.rotate.degrees;
			CT.trans.rotate(node, opts);
		};
		CT.trans.trans(opts);
		return node;
	},
	translate: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.translate);
		opts.node = node;
		if (opts.axis)
			opts[opts.axis] = opts.value;
		opts.value = "translate3d(" + opts.x + "px," + opts.y + "px," + opts.z + "px)",
		CT.trans.trans(opts);
	},
	wobble: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.wobble);
		CT.trans.translate(node, {
			axis: opts.axis,
			ease: "ease-out",
			value: opts.radius,
			duration: opts.duration,
			cb: function() {
				CT.trans.translate(node, {
					axis: opts.axis,
					ease: "ease-in-out",
					value: -opts.radius,
					duration: opts.duration * 2,
					cb: function() {
						CT.trans.translate(node, {
							axis: opts.axis,
							ease: "ease-in",
							value: 0,
							duration: opts.duration
						});
					}
				});
			}
		});
	},
	pan: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.pan);
		var setV = function(cb) {
			node._vertical = CT.dom.cover(node);
			node._prop = node._vertical ? "y" : "x",
			node._dim = node._vertical ? "clientHeight" : "clientWidth";
			node._bound = node.parentNode[node._dim] - node[node._dim];
			cb && setTimeout(cb);
		}, controllercb = function() {
			if (node._vertical == undefined)
				return setV(controllercb);
			opts[node._prop] = opts[node._prop] ? 0 : node._bound;
			CT.trans.translate(node, opts);
		}, controller = new CT.trans.Controller(controllercb);
		node.onload = function() {
			setTimeout(controller.resume); // wait a tic!
		};
		CT.onresize(node.onload); // ??? <-- looks wrong
		opts.cb = controller.tick;
		return controller;
	},
	resize: function(node, opts) {
		var width = opts.width,
			height = opts.height,
			cb = opts.cb;
		delete opts.width;
		delete opts.height;
		delete opts.cb;
		opts.node = node;
		opts = CT.merge(opts, CT.trans._.defaults.resize);
		if (width) {
			CT.trans.trans(CT.merge({
				property: "width",
				value: width
			}, opts));
		}
		if (height) {
			CT.trans.trans(CT.merge({
				property: "height",
				value: height
			}, opts));
		}
		if (cb) {
			CT.trans.trans(CT.merge({
				cb: cb
			}, opts));
		}
	},
	fadeIn: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.fadeIn);
		opts.node = node;
		CT.trans.trans(opts);
	},
	fadeOut: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.fadeOut);
		opts.node = node;
		CT.trans.trans(opts);
	},
	pulse: function(node, opts, wait) {
		opts = CT.merge(opts, CT.trans._.defaults.pulse);
		var faded, controller = new CT.trans.Controller(function() {
			faded = !faded;
			if (faded) {
				setTimeout(function() {
					CT.trans.fadeIn(node, opts);
				}, opts.wait);
			} else
				CT.trans.fadeOut(node, opts);
		});
		opts.cb = controller.tick;
		if (!wait)
			controller.tick();
		return controller;
	}
};

CT.trans.Controller = CT.Class({
	CLASSNAME: "CT.trans.Controller",
	pause: function() {
		this._paused = true;
	},
	resume: function() {
		if (this._paused != false) {
			this._paused = false;
			if (this._stalled) {
				this._stalled = false;
				this.cb();
			}
		}
	},
	active: function() {
		return !this._paused;
	},
	tick: function() {
		if (this.active())
			this.cb();
		else
			this._stalled = true;
	},
	init: function(cb) {
		this.cb = cb;
		this._stalled = true;
	}
});<|endoftext|>


<commit_before>/*
This module provides convenience functions for messing
around with DOM elements via CSS transitions. Have at it.

### Try out these functions:
	CT.trans.rotate(node, opts)
	CT.trans.translate(node, opts)
	CT.trans.pan(node, opts)
	CT.trans.trans(opts)
	CT.trans.setVendorPrefixed(node, property, value)
	 - sets CSS properties for all vendor prefixes
	   - [ "-webkit-", "-moz-", "-ms-", "-o-", "" ]

### And here are the default options:
	trans: {
		duration: 500,
		property: "*",
		ease: "ease-in-out"
	},
	rotate: {
		degrees: 180,
		duration: 1000,
		property: "transform",
		ease: "linear",
		prefix: true
	},
	translate: {
		duration: 300,
		property: "transform",
		ease: "ease-out",
		prefix: true,
		x: 0,
		y: 0,
		z: 0
	},
	pan: {
		duration: 5000,
		property: "background-position",
		ease: "linear",
		value: "right"
	}

TODO: let's add some more, like scale and fade.
*/

CT.trans = {
	_: {
		vender_prefixes: [ "-webkit-", "-moz-", "-ms-", "-o-", "" ],
		tswap: { transform: "-webkit-transform" }, // mobile safari transitions
		enames: [ "webkitTransitionEnd", "mozTransitionEnd", "oTransitionEnd", "transitionend"],
		defaults: {
			trans: {
				duration: 500,
				property: "*",
				ease: "ease-in-out"
			},
			rotate: {
				degrees: 180,
				duration: 1000,
				property: "transform",
				ease: "linear",
				prefix: true
			},
			translate: {
				duration: 300,
				property: "transform",
				ease: "ease-out",
				prefix: true,
				x: 0,
				y: 0,
				z: 0
			},
			pan: {
				duration: 5000,
				property: "background-position",
				ease: "linear",
				value: "right"
			}
		}
	},
	setVenderPrefixed: function(node, property, value) {
	    for (var i = 0; i < CT.trans._.vender_prefixes.length; i++)
	        node.style[CT.trans._.vender_prefixes[i] + property] = value;
	},
	trans: function(opts) {
		// opts: node, property, value, cb, duration, ease prefix
		opts = CT.merge(opts, CT.trans._.defaults.trans);
		if (opts.node) {
			var prevTrans = opts.node.style.transition || "",
				transComponent = (CT.trans._.tswap[opts.property] || opts.property)
		        	+ " " + opts.duration + "ms " + opts.ease,
		        fullTrans = prevTrans ? (prevTrans + ", " + transComponent) : transComponent;
		    CT.trans.setVenderPrefixed(opts.node, "transition", fullTrans);
		}
	    if (opts.cb) {
	        var transTimeout, wrapper = function () {
	            opts.node && CT.trans.setVenderPrefixed(opts.node, "transition", prevTrans);
	            clearTimeout(transTimeout);
	            transTimeout = null;
	            if (opts.node) CT.trans._.enames.forEach(function(ename) {
		            opts.node.removeEventListener(ename, wrapper, false);
            	});
	            opts.cb();
	        }
            if (opts.node) CT.trans._.enames.forEach(function(ename) {
	            opts.node.addEventListener(ename, wrapper, false);
        	});
	        transTimeout = setTimeout(wrapper, opts.duration);
	    }
	    if (opts.property && opts.property != "*") {
	        if (opts.prefix)
	            CT.trans.setVenderPrefixed(opts.node, opts.property, opts.value);
	        else
	            opts.node.style[opts.property] = opts.value;
	    }
	},
	rotate: function(node, opts) {
		// opts may include: duration, degrees, cb, ease, forever
		opts = CT.merge(opts, CT.trans._.defaults.rotate);
		opts.node = node;
		opts.value = "rotate(" + opts.degrees + "deg)";
		if (opts.forever) opts.cb = function() {
			opts.degrees += CT.trans._.defaults.rotate.degrees;
			CT.trans.rotate(node, opts);
		};
		CT.trans.trans(opts);
	},
	translate: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.translate);
		opts.node = node;
		opts.value = "translate3d(" + opts.x + "px," + opts.y + "px," + opts.z + "px)",
		CT.trans.trans(opts);
	},
	pan: function(node, opts) { // for background-position
		opts = CT.merge(opts, CT.trans._.defaults.pan);
		opts.node = node;
		opts.cb = function() {
			opts.value = opts.value == "left" ? "right" : "left";
			CT.trans.pan(node, opts);
		}
		CT.trans.trans(opts);
	}
};<commit_msg>CT.trans: fadeIn(), fadeOut()<commit_after>/*
This module provides convenience functions for messing
around with DOM elements via CSS transitions. Have at it.

### Try out these functions:
	CT.trans.rotate(node, opts)
	CT.trans.translate(node, opts)
	CT.trans.pan(node, opts)
	CT.trans.trans(opts)
	CT.trans.setVendorPrefixed(node, property, value)
	 - sets CSS properties for all vendor prefixes
	   - [ "-webkit-", "-moz-", "-ms-", "-o-", "" ]

### And here are the default options:
	trans: {
		duration: 500,
		property: "*",
		ease: "ease-in-out"
	},
	rotate: {
		degrees: 180,
		duration: 1000,
		property: "transform",
		ease: "linear",
		prefix: true
	},
	translate: {
		duration: 300,
		property: "transform",
		ease: "ease-out",
		prefix: true,
		x: 0,
		y: 0,
		z: 0
	},
	pan: {
		duration: 5000,
		property: "background-position",
		ease: "linear",
		value: "right"
	},
	fadeIn: {
		duration: 1000,
		property: "opacity",
		value: 1
	},
	fadeOut: {
		duration: 1000,
		property: "opacity",
		value: 0
	}

TODO: let's add some more, like scale.
*/

CT.trans = {
	_: {
		vender_prefixes: [ "-webkit-", "-moz-", "-ms-", "-o-", "" ],
		tswap: { transform: "-webkit-transform" }, // mobile safari transitions
		enames: [ "webkitTransitionEnd", "mozTransitionEnd", "oTransitionEnd", "transitionend"],
		defaults: {
			trans: {
				duration: 500,
				property: "*",
				ease: "ease-in-out"
			},
			rotate: {
				degrees: 180,
				duration: 1000,
				property: "transform",
				ease: "linear",
				prefix: true
			},
			translate: {
				duration: 300,
				property: "transform",
				ease: "ease-out",
				prefix: true,
				x: 0,
				y: 0,
				z: 0
			},
			pan: {
				duration: 5000,
				property: "background-position",
				ease: "linear",
				value: "right"
			},
			fadeIn: {
				duration: 1000,
				property: "opacity",
				value: 1
			},
			fadeOut: {
				duration: 1000,
				property: "opacity",
				value: 0
			}
		}
	},
	setVenderPrefixed: function(node, property, value) {
	    for (var i = 0; i < CT.trans._.vender_prefixes.length; i++)
	        node.style[CT.trans._.vender_prefixes[i] + property] = value;
	},
	trans: function(opts) {
		// opts: node, property, value, cb, duration, ease, prefix
		opts = CT.merge(opts, CT.trans._.defaults.trans);
		if (opts.node) {
			var prevTrans = opts.node.style.transition || "",
				transComponent = (CT.trans._.tswap[opts.property] || opts.property)
		        	+ " " + opts.duration + "ms " + opts.ease,
		        fullTrans = prevTrans ? (prevTrans + ", " + transComponent) : transComponent;
		    CT.trans.setVenderPrefixed(opts.node, "transition", fullTrans);
		}
	    if (opts.cb) {
	        var transTimeout, wrapper = function () {
	            opts.node && CT.trans.setVenderPrefixed(opts.node, "transition", prevTrans);
	            clearTimeout(transTimeout);
	            transTimeout = null;
	            if (opts.node) CT.trans._.enames.forEach(function(ename) {
		            opts.node.removeEventListener(ename, wrapper, false);
            	});
	            opts.cb();
	        }
            if (opts.node) CT.trans._.enames.forEach(function(ename) {
	            opts.node.addEventListener(ename, wrapper, false);
        	});
	        transTimeout = setTimeout(wrapper, opts.duration);
	    }
	    if (opts.property && opts.property != "*") {
	        if (opts.prefix)
	            CT.trans.setVenderPrefixed(opts.node, opts.property, opts.value);
	        else
	            opts.node.style[opts.property] = opts.value;
	    }
	},
	rotate: function(node, opts) {
		// opts may include: duration, degrees, cb, ease, forever
		opts = CT.merge(opts, CT.trans._.defaults.rotate);
		opts.node = node;
		opts.value = "rotate(" + opts.degrees + "deg)";
		if (opts.forever) opts.cb = function() {
			opts.degrees += CT.trans._.defaults.rotate.degrees;
			CT.trans.rotate(node, opts);
		};
		CT.trans.trans(opts);
	},
	translate: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.translate);
		opts.node = node;
		opts.value = "translate3d(" + opts.x + "px," + opts.y + "px," + opts.z + "px)",
		CT.trans.trans(opts);
	},
	pan: function(node, opts) { // for background-position
		opts = CT.merge(opts, CT.trans._.defaults.pan);
		opts.node = node;
		opts.cb = function() {
			opts.value = opts.value == "left" ? "right" : "left";
			CT.trans.pan(node, opts);
		}
		CT.trans.trans(opts);
	},
	fadeIn: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.fadeIn);
		opts.node = node;
		CT.trans.trans(opts);
	},
	fadeOut: function(node, opts) {
		opts = CT.merge(opts, CT.trans._.defaults.fadeOut);
		opts.node = node;
		CT.trans.trans(opts);
	}
};<|endoftext|>


<commit_before>CT.video = {
	// video link detection and parsing
	"rawVidTypes": ["mp4", "ogg", "webm"],
	"player2url": {
	    "google": "video.google.com?docid=",
	    "youtube": "www.youtube.com/watch?v=",
	    "vimeo": "vimeo.com/",
	    "ustream": "www.ustream.tv/recorded/",
	    "mp4": "", "ogg": "", "webm": ""
	},
	"embed_url": {
	    "youtube": "http://www.youtube.com/embed/",
	    "vimeo": "//player.vimeo.com/video/"
	},
	"urlFromData": function(player, docid) {
	    return player ? ("http://" + CT.video.player2url[player] + docid) : "";
	},
	"getQSParam": function(url, param) {
	    var s = url.indexOf(param + "=") + param.length + 1,
	        e = url.indexOf("&", s);
	    if (e == -1)
	        e = url.indexOf("#", s);
	    if (e == -1)
	        return url.slice(s);
	    return url.slice(s, e);
	},
	"docidFromUrl": function(url) {
	    if (url.indexOf("video.google.com") != -1)
	        return CT.video.getQSParam(url, "docid");
	    else if (url.indexOf("youtube.com") != -1)
	        return CT.video.getQSParam(url, "v");
	    else if (url.indexOf("vimeo.com") != -1)
	        return url.slice(url.lastIndexOf('/') + 1);
	    else if (url.indexOf("ustream.tv/recorded/") != -1)
	        return url.split("recorded/")[1].split("?")[0];
	    var spliturl = url.split('.'),
	        ext = spliturl[spliturl.length - 1];
	    if (CT.video.rawVidTypes.indexOf(ext) != -1) // eventually do more about ssl
	        return url.replace("http://", "").replace("https://", "");
	    return "";
	},
	"playerFromUrl": function(url) {
	    if (url.indexOf("video.google.com") != -1)
	        return "google";
	    if (url.indexOf("youtube.com") != -1)
	        return "youtube";
	    if (url.indexOf("vimeo.com") != -1)
	        return "vimeo";
	    if (url.indexOf("ustream.tv") != -1)
	        return "ustream";
	    var spliturl = url.split('.'),
	        ext = spliturl[spliturl.length - 1];
	    if (CT.video.rawVidTypes.indexOf(ext) != -1)
	        return ext;
	    return "";
	},
	"videoData": function(vlink) {
	    var player = CT.video.playerFromUrl(vlink);
	    return player ? {
	        player: player,
	        docid: CT.video.docidFromUrl(vlink)
	    } : null;
	},
	"embed": function(video, small) {
	    var w = small ? 375 : 400;
	    var h = small ? 315 : 335;
	    if (video.player == "google")
	        return "<embed width=" + w + " height=" + h + " id=VideoPlayback src=http://video.google.com/googleplayer.swf?docid=" + video.docid + "&hl=en&fs=true allowFullScreen=true allowScriptAccess=always type=application/x-shockwave-flash> </embed>";
	    else if (video.player == "youtube" || video.player == "vimeo")
	        return "";
	    else if (video.player == "ustream")
	        return "<object type=application/x-shockwave-flash data=http://static-cdn1.ustream.tv/swf/live/viewerqos:21.swf width=" + w + " height=" + h + "id=utv" + video.docid + " name=utv" + video.docid + "><param name=flashvars value=autoplay=true&locale=en_US&referrer=http%3A%2F%2Fwww.ustream.tv%2Frecorded%2F" + video.docid + "%3Futm_campaign%3Dustre.am%26utm_source%3Dustre.am%2F%3A44gEy%26utm_medium%3Dsocial%26utm_content%3D20150324210416&autoResize=false&enablejsapi=true&sv=6&volume=1&ts=1427256261325&vid=" + video.docid + "&loc=" + video.docid + "&hasticket=false><param name=allowfullscreen value=true><param name=allowscriptaccess value=always><param name=bgcolor value=000000><param name=wmode value=transparent></object>"
	    else if (CT.video.rawVidTypes.indexOf(video.player) != -1)
	        return "<video width=" + w + " height=" + h + " controls><source src=http://" + video.docid + " type=video/" + video.player + "></video>";
	    else
	        alert("unknown video player: "+video.player);
	}
};<commit_msg>CT.video docstring<commit_after>/*
This module supports video playback.

video players
-------------
We support Google Video, YouTube, Vimeo, and uStream.

raw formats
-----------
We support mp4, ogg, and webm.

Typically, you'll want to use the embed() function.

CT.video.embed(video, small) - returns stringified html
 - video (string): link to video content
 - small (bool): if true, make it smaller

TODO: replace 'small' bool with flexible styling.
*/

CT.video = {
	// video link detection and parsing
	"rawVidTypes": ["mp4", "ogg", "webm"],
	"player2url": {
	    "google": "video.google.com?docid=",
	    "youtube": "www.youtube.com/watch?v=",
	    "vimeo": "vimeo.com/",
	    "ustream": "www.ustream.tv/recorded/",
	    "mp4": "", "ogg": "", "webm": ""
	},
	"embed_url": {
	    "youtube": "http://www.youtube.com/embed/",
	    "vimeo": "//player.vimeo.com/video/"
	},
	"urlFromData": function(player, docid) {
	    return player ? ("http://" + CT.video.player2url[player] + docid) : "";
	},
	"getQSParam": function(url, param) {
	    var s = url.indexOf(param + "=") + param.length + 1,
	        e = url.indexOf("&", s);
	    if (e == -1)
	        e = url.indexOf("#", s);
	    if (e == -1)
	        return url.slice(s);
	    return url.slice(s, e);
	},
	"docidFromUrl": function(url) {
	    if (url.indexOf("video.google.com") != -1)
	        return CT.video.getQSParam(url, "docid");
	    else if (url.indexOf("youtube.com") != -1)
	        return CT.video.getQSParam(url, "v");
	    else if (url.indexOf("vimeo.com") != -1)
	        return url.slice(url.lastIndexOf('/') + 1);
	    else if (url.indexOf("ustream.tv/recorded/") != -1)
	        return url.split("recorded/")[1].split("?")[0];
	    var spliturl = url.split('.'),
	        ext = spliturl[spliturl.length - 1];
	    if (CT.video.rawVidTypes.indexOf(ext) != -1) // eventually do more about ssl
	        return url.replace("http://", "").replace("https://", "");
	    return "";
	},
	"playerFromUrl": function(url) {
	    if (url.indexOf("video.google.com") != -1)
	        return "google";
	    if (url.indexOf("youtube.com") != -1)
	        return "youtube";
	    if (url.indexOf("vimeo.com") != -1)
	        return "vimeo";
	    if (url.indexOf("ustream.tv") != -1)
	        return "ustream";
	    var spliturl = url.split('.'),
	        ext = spliturl[spliturl.length - 1];
	    if (CT.video.rawVidTypes.indexOf(ext) != -1)
	        return ext;
	    return "";
	},
	"videoData": function(vlink) {
	    var player = CT.video.playerFromUrl(vlink);
	    return player ? {
	        player: player,
	        docid: CT.video.docidFromUrl(vlink)
	    } : null;
	},
	"embed": function(video, small) {
	    var w = small ? 375 : 400;
	    var h = small ? 315 : 335;
	    if (video.player == "google")
	        return "<embed width=" + w + " height=" + h + " id=VideoPlayback src=http://video.google.com/googleplayer.swf?docid=" + video.docid + "&hl=en&fs=true allowFullScreen=true allowScriptAccess=always type=application/x-shockwave-flash> </embed>";
	    else if (video.player == "youtube" || video.player == "vimeo")
	        return "";
	    else if (video.player == "ustream")
	        return "<object type=application/x-shockwave-flash data=http://static-cdn1.ustream.tv/swf/live/viewerqos:21.swf width=" + w + " height=" + h + "id=utv" + video.docid + " name=utv" + video.docid + "><param name=flashvars value=autoplay=true&locale=en_US&referrer=http%3A%2F%2Fwww.ustream.tv%2Frecorded%2F" + video.docid + "%3Futm_campaign%3Dustre.am%26utm_source%3Dustre.am%2F%3A44gEy%26utm_medium%3Dsocial%26utm_content%3D20150324210416&autoResize=false&enablejsapi=true&sv=6&volume=1&ts=1427256261325&vid=" + video.docid + "&loc=" + video.docid + "&hasticket=false><param name=allowfullscreen value=true><param name=allowscriptaccess value=always><param name=bgcolor value=000000><param name=wmode value=transparent></object>"
	    else if (CT.video.rawVidTypes.indexOf(video.player) != -1)
	        return "<video width=" + w + " height=" + h + " controls><source src=http://" + video.docid + " type=video/" + video.player + "></video>";
	    else
	        alert("unknown video player: "+video.player);
	}
};<|endoftext|>


<commit_before>var AttributeList = module.exports = function AttributeList(attributes) {
  this.mergeAttributes(attributes);
};

var dataTypes = AttributeList.dataTypes = {
  'audio'      : 'quoted-string',
  'autoselect' : 'boolean',
  'bandwidth'  : 'decimal-integer',
  'byterange'  : 'enumerated-string',
  'codecs'     : 'quoted-string',
  'default'    : 'boolean',
  'duration'   : 'decimal-floating-point',
  'forced'     : 'boolean',
  'group-id'   : 'quoted-string',
  'language'   : 'quoted-string',
  'name'       : 'quoted-string',
  'program-id' : 'decimal-integer',
  'resolution' : 'decimal-resolution',
  'subtitles'  : 'quoted-string',
  'title'      : 'enumerated-string',
  'type'       : 'enumerated-string',
  'uri'        : 'quoted-string',
  'video'      : 'quoted-string'
};

AttributeList.prototype.mergeAttributes = function mergeAttributes(attributes) {
  var self = this;
  if (Array.isArray(attributes)) {
    attributes.forEach(function(attribute) {
      self.set(attribute.key, attribute.value);
    });
  }
};

AttributeList.prototype.get = function getValue(key) {
  return this[key];
};

AttributeList.prototype.set = function setValue(key, value) {
  key = key.toLowerCase();
  this[key] = parse[dataTypes[key] || 'unknown'](value, key);

  return this;
};

AttributeList.prototype.getCoerced = function getCoerced(key) {
  return coerce[dataTypes[key] || 'unknown'](this[key]);
};

AttributeList.prototype.toString = function toString() {
  var keyValues = [];
  var self = this;
  Object.keys(this).forEach(function(key) {
    var value = self.getCoerced(key);
    keyValues.push(key.toUpperCase() + '=' + value);
  });

  return keyValues.join(', ');
};

var coerce = {
  'boolean': function coerceBoolean(value) {
    return value ? 'YES' : 'NO';
  },
  'decimal-floating-point': parseFloat,
  'decimal-integer': function coerceDecimalInteger(value) {
    return parseInt(value, 10);
  },
  'decimal-resolution': function coerceDecimalResolution(value) {
    if (Array.isArray(value)) {
      return value.join('x');
    } else {
      return value;
    }
  },
  'enumerated-string': function coerceEnumeratedString(value) {
    return value;
  },
  'quoted-string': function coerceQuotedString(value) {
    return '"' + value.replace(/"/g, '\\"') + '"';
  },
  'unknown': function coerceUnknown(value) {
    return value;
  }
};

var parse = {
  'boolean': function parseBoolean(value) {
    return typeof value == 'boolean'
      ? value
      : (value == 'YES' ? true : false);
  },
  'decimal-floating-point': parseFloat,
  'decimal-integer': function parseDecimalInteger(value) {
    return parseInt(value, 10);
  },
  'decimal-resolution': function coerceDecimalResolution(value) {
    return value.split('x').map(parse['decimal-integer']);
  },
  'enumerated-string': function parseEnumeratedString(value) {
    return value;
  },
  'quoted-string': function parseQuotedString(value) {
    return value.slice(1,-1);
  },
  'unknown': function parseUnknown(value, key) {
    console.error('Handling value:', value, ' for unknown key:', key);
    return value;
  }
};<commit_msg>set attributes on internal attribute object<commit_after>var AttributeList = module.exports = function AttributeList(attributes) {
  this.attributes = {};
  this.mergeAttributes(attributes);
};

var dataTypes = AttributeList.dataTypes = {
  'audio'      : 'quoted-string',
  'autoselect' : 'boolean',
  'bandwidth'  : 'decimal-integer',
  'byterange'  : 'enumerated-string',
  'codecs'     : 'quoted-string',
  'default'    : 'boolean',
  'duration'   : 'decimal-floating-point',
  'forced'     : 'boolean',
  'group-id'   : 'quoted-string',
  'language'   : 'quoted-string',
  'name'       : 'quoted-string',
  'program-id' : 'decimal-integer',
  'resolution' : 'decimal-resolution',
  'subtitles'  : 'quoted-string',
  'title'      : 'enumerated-string',
  'type'       : 'enumerated-string',
  'uri'        : 'quoted-string',
  'video'      : 'quoted-string'
};

AttributeList.prototype.mergeAttributes = function mergeAttributes(attributes) {
  var self = this;
  if (Array.isArray(attributes)) {
    attributes.forEach(function(attribute) {
      self.set(attribute.key, attribute.value);
    });
  }
};

AttributeList.prototype.get = function getValue(key) {
  return this.attributes[key];
};

AttributeList.prototype.set = function setValue(key, value) {
  key = key.toLowerCase();
  this.attributes[key] = parse[dataTypes[key] || 'unknown'](value, key);

  return this;
};

AttributeList.prototype.getCoerced = function getCoerced(key) {
  return coerce[dataTypes[key] || 'unknown'](this.get(key));
};

AttributeList.prototype.toString = function toString() {
  var keyValues = [];
  var self = this;
  Object.keys(this).forEach(function(key) {
    var value = self.getCoerced(key);
    keyValues.push(key.toUpperCase() + '=' + value);
  });

  return keyValues.join(', ');
};

var coerce = {
  'boolean': function coerceBoolean(value) {
    return value ? 'YES' : 'NO';
  },
  'decimal-floating-point': parseFloat,
  'decimal-integer': function coerceDecimalInteger(value) {
    return parseInt(value, 10);
  },
  'decimal-resolution': function coerceDecimalResolution(value) {
    if (Array.isArray(value)) {
      return value.join('x');
    } else {
      return value;
    }
  },
  'enumerated-string': function coerceEnumeratedString(value) {
    return value;
  },
  'quoted-string': function coerceQuotedString(value) {
    return '"' + value.replace(/"/g, '\\"') + '"';
  },
  'unknown': function coerceUnknown(value) {
    return value;
  }
};

var parse = {
  'boolean': function parseBoolean(value) {
    return typeof value == 'boolean'
      ? value
      : (value == 'YES' ? true : false);
  },
  'decimal-floating-point': parseFloat,
  'decimal-integer': function parseDecimalInteger(value) {
    return parseInt(value, 10);
  },
  'decimal-resolution': function coerceDecimalResolution(value) {
    return value.split('x').map(parse['decimal-integer']);
  },
  'enumerated-string': function parseEnumeratedString(value) {
    return value;
  },
  'quoted-string': function parseQuotedString(value) {
    return value.slice(1,-1);
  },
  'unknown': function parseUnknown(value, key) {
    console.error('Handling value:', value, ' for unknown key:', key);
    return value;
  }
};<|endoftext|>


<commit_before>import { remove, findIndex } from 'lodash'
import { focusIn } from '../utils/dom'
import Vue from 'vue'

class FocusContext {
  /**
   *
   * @param {Element} root Root element for focus context
   * @param {=Element} source Previous focused element
   * @param {=Boolean} trap Whether focus should be trapped inside root
   * @param {=Element} preferred Where we should start searching for focusable elements
   */
  constructor (root, { source, trap = false, preferred }) {
    if (!root) {
      throw new Error('Root must be specified to create a FocusContext instance.')
    }

    this.root = root
    this.source = source
    this.trap = trap
    this.preferred = preferred

    this.outsideStartHandler = () => {
      this.focusAt(-2, true)
    }
    this.outsideEndHandler = () => {
      this.focusAt(1, true)
    }

    this.init()
  }

  /**
   * Initalize a focus context:
   * 1. trap focus events inside `root`
   * 2. focus `root` by default
   */
  init () {
    this.focusAt()

    if (this.trap) {
      let before = document.createElement('div')
      before.tabIndex = 0
      let after = before.cloneNode()

      before.addEventListener('focus', this.outsideStartHandler, true)
      after.addEventListener('focus', this.outsideEndHandler, true)

      this.root.insertBefore(before, this.root.firstChild)
      this.root.appendChild(after)

      this.wardBefore = before
      this.wardAfter = after
    }
  }

  focusAt (index = 0, ignoreAutofocus) {
    Vue.nextTick(() => {
      if (!focusIn(this.preferred || this.root, index, ignoreAutofocus)) {
        this.root.focus()
      }
    })
  }

  destroy () {
    let { trap, source, wardBefore, wardAfter } = this
    if (trap) {
      wardBefore.removeEventListener('focus', this.outsideStartHandler, true)
      wardAfter.removeEventListener('focus', this.outsideEndHandler, true)
      this.root.removeChild(wardBefore)
      this.root.removeChild(wardAfter)
    }
    if (source && typeof source.focus === 'function') {
      this.source = null

      // restore focus in a macro task to prevent
      // triggering events on the original focus
      setTimeout(() => {
        source.focus()
      }, 0)
    }
    this.preferred = null
    this.root = null
  }
}

export class FocusManager {
  /**
   * Global focus history stack
   * @type Array.<FocusContext>
   * @private
   */
  stack = []

  createContext (root, options = {}) {
    let context = new FocusContext(root, options)
    this.stack.push(context)
    return context
  }

  toTop (context) {
    this.detach(context)
    this.stack.push(context)
  }

  detach (context) {
    let stack = this.stack
    let i = findIndex(stack, context)
    if (i === -1) {
      return
    }

    if (i < stack.length - 1) {
      stack[i + 1].source = context.source
    }

    remove(this.stack, item => item === context)
    return context
  }

  remove (context) {
    this.detach(context)
    context.destroy()
  }
}

export default new FocusManager()
<commit_msg>fix #255<commit_after>import { remove, findIndex } from 'lodash'
import { focusIn } from '../utils/dom'
import Vue from 'vue'

class FocusContext {
  /**
   *
   * @param {Element} root Root element for focus context
   * @param {=Element} source Previous focused element
   * @param {=Boolean} trap Whether focus should be trapped inside root
   * @param {=Element} preferred Where we should start searching for focusable elements
   */
  constructor (root, { source, trap = false, preferred }) {
    if (!root) {
      throw new Error('Root must be specified to create a FocusContext instance.')
    }

    this.root = root
    this.source = source
    this.trap = trap
    this.preferred = preferred

    this.outsideStartHandler = () => {
      this.focusAt(-2, true)
    }
    this.outsideEndHandler = () => {
      this.focusAt(1, true)
    }

    this.init()
  }

  /**
   * Initalize a focus context:
   * 1. trap focus events inside `root`
   * 2. focus `root` by default
   */
  init () {
    if (this.trap) {
      let before = document.createElement('div')
      before.tabIndex = 0
      let after = before.cloneNode()

      before.addEventListener('focus', this.outsideStartHandler, true)
      after.addEventListener('focus', this.outsideEndHandler, true)

      this.root.insertBefore(before, this.root.firstChild)
      this.root.appendChild(after)

      this.wardBefore = before
      this.wardAfter = after
    }

    // skip wardBefore if trapping
    this.focusAt(this.trap ? 1 : 0)
  }

  focusAt (index = 0, ignoreAutofocus) {
    Vue.nextTick(() => {
      if (!focusIn(this.preferred || this.root, index, ignoreAutofocus)) {
        this.root.focus()
      }
    })
  }

  destroy () {
    let { trap, source, wardBefore, wardAfter } = this
    if (trap) {
      wardBefore.removeEventListener('focus', this.outsideStartHandler, true)
      wardAfter.removeEventListener('focus', this.outsideEndHandler, true)
      this.root.removeChild(wardBefore)
      this.root.removeChild(wardAfter)
    }
    if (source && typeof source.focus === 'function') {
      this.source = null

      // restore focus in a macro task to prevent
      // triggering events on the original focus
      setTimeout(() => {
        source.focus()
      }, 0)
    }
    this.preferred = null
    this.root = null
  }
}

export class FocusManager {
  /**
   * Global focus history stack
   * @type Array.<FocusContext>
   * @private
   */
  stack = []

  createContext (root, options = {}) {
    let context = new FocusContext(root, options)
    this.stack.push(context)
    return context
  }

  toTop (context) {
    this.detach(context)
    this.stack.push(context)
  }

  detach (context) {
    let stack = this.stack
    let i = findIndex(stack, context)
    if (i === -1) {
      return
    }

    if (i < stack.length - 1) {
      stack[i + 1].source = context.source
    }

    remove(this.stack, item => item === context)
    return context
  }

  remove (context) {
    this.detach(context)
    context.destroy()
  }
}

export default new FocusManager()
<|endoftext|>