<commit_before>Cobalt.android_adapter.protoype = function() {
  var self = this;

  var Adapter = {
    //
    //ANDROID ADAPTER
    //
    init: function() {
      self.platform = {
        is: "Android"
      };
    },
    // handle events sent by native side
    handleEvent: function(json) {
      self.log("received event", json.event);
      if (self.events && typeof self.events[json.event] === "function") {
        self.events[json.event](json.data, json.callback);
      } else {
        switch (json.event) {
          case "onBackButtonPressed":
            self.log('sending OK for a native back');
            self.sendCallback(json.callback, {
              value: true
            });
            break;
          default:
            Adapter.handleUnknown(json);
            break;
        }
      }
    },
    //send native stuff
    send: function(obj) {
      if (obj && !self.debugInBrowser) {
        self.divLog('sending', obj);
        try {
          Android.onCobaltMessage(JSON.stringify(obj));
        } catch (e) {
          self.log('ERROR : cant connect to native');
        }

      }
    },
    //modal stuffs. really basic on ios, more complex on android.
    navigateToModal: function(options) {
      self.send({
        "type": "navigation",
        "action": "modal",
        data: {
          page: options.page,
          controller: options.controller,
          data: options.data
        }
      }, 'cobalt.adapter.storeModalInformations');
    },
    dismissFromModal: function(data) {
      var dismissInformations = self.storage.get("dismissInformations");
      if (dismissInformations && dismissInformations.page && dismissInformations.controller) {
        self.send({
          "type": "navigation",
          "action": "dismiss",
          data: {
            page: dismissInformations.page,
            controller: dismissInformations.controller,
            data: data
          }
        });
        self.storage.remove("dismissInformations");
      } else {
        self.log("WANRING : dismissInformations are not available in storage");
      }

    },
    storeModalInformations: function(params) {
      self.storage.set("dismissInformations", params);
    },
    //localStorage stuff
    initStorage: function() {
      //on android, try to bind window.localStorage to Android LocalStorage
      try {
        window.localStorage = LocalStorage;
      } catch (e) {
        self.log("LocalStorage WARNING : can't find android class LocalStorage. switching to raw localStorage")
      }
      return self.storage.enable();
    },
    //datePicker stuff
    datePicker: {
      init: function(inputs) {

        self.utils.each(inputs, function() {
          var input = this;
          var id = self.utils.attr(input, 'id');

          self.log('datePicker setted with value=' + input.value);
          self.utils.attr(input, 'type', 'text');
          self.datePicker.enhanceFieldValue.apply(input);

          input.addEventListener('focus', function() {
            self.log('show formPicker date for date #', id);
            input.blur();
            var previousDate = self.storage.get('CobaltDatePickerValue_' + id);
            if (!previousDate) {
              var d = new Date();
              previousDate = {
                year: d.getFullYear(),
                day: d.getDate(),
                month: d.getMonth() + 1
              };
            }
            self.send({
              type: "ui",
              control: "picker",
              data: {
                type: "date",
                date: previousDate,
                texts: self.datePicker.texts
              }
            }, function(newDate) {
              if (newDate && newDate.year) {
                input.value = newDate.year + '-' + newDate.month + '-' + newDate.day;
                self.log('setting storage date ', newDate);
                self.storage.set('CobaltDatePickerValue_' + id, newDate);
                self.datePicker.enhanceFieldValue.apply(input);
              } else {
                self.log('removing storage date');
                input.value = "";
                self.storage.remove('CobaltDatePickerValue_' + id);
              }
            });
            return false;

          }, false);

        });
      },
      val: function(input) {
        var date = self.storage.get('CobaltDatePickerValue_' + self.utils.attr(input, 'id'));
        if (date) {
          var str_date = self.datePicker.stringifyDate(date);
          self.log('returning storage date ', str_date);
          return str_date;
        }
        return undefined;
      }
    },

    //default behaviours
    handleCallback: self.defaultBehaviors.handleCallback,
    handleUnknown: self.defaultBehaviors.handleUnknown
  };

  return Adapter;
};
<commit_msg>Prototype on android<commit_after>Cobalt.prototype.android_adapter = function() {
  var self = this;

  var Adapter = {
    //
    //ANDROID ADAPTER
    //
    init: function() {
      self.platform = {
        is: "Android"
      };
    },
    // handle events sent by native side
    handleEvent: function(json) {
      self.log("received event", json.event);
      if (self.events && typeof self.events[json.event] === "function") {
        self.events[json.event](json.data, json.callback);
      } else {
        switch (json.event) {
          case "onBackButtonPressed":
            self.log('sending OK for a native back');
            self.sendCallback(json.callback, {
              value: true
            });
            break;
          default:
            Adapter.handleUnknown(json);
            break;
        }
      }
    },
    //send native stuff
    send: function(obj) {
      if (obj && !self.debugInBrowser) {
        self.divLog('sending', obj);
        try {
          Android.onCobaltMessage(JSON.stringify(obj));
        } catch (e) {
          self.log('ERROR : cant connect to native');
        }

      }
    },
    //modal stuffs. really basic on ios, more complex on android.
    navigateToModal: function(options) {
      self.send({
        "type": "navigation",
        "action": "modal",
        data: {
          page: options.page,
          controller: options.controller,
          data: options.data
        }
      }, 'cobalt.adapter.storeModalInformations');
    },
    dismissFromModal: function(data) {
      var dismissInformations = self.storage.get("dismissInformations");
      if (dismissInformations && dismissInformations.page && dismissInformations.controller) {
        self.send({
          "type": "navigation",
          "action": "dismiss",
          data: {
            page: dismissInformations.page,
            controller: dismissInformations.controller,
            data: data
          }
        });
        self.storage.remove("dismissInformations");
      } else {
        self.log("WANRING : dismissInformations are not available in storage");
      }

    },
    storeModalInformations: function(params) {
      self.storage.set("dismissInformations", params);
    },
    //localStorage stuff
    initStorage: function() {
      //on android, try to bind window.localStorage to Android LocalStorage
      try {
        window.localStorage = LocalStorage;
      } catch (e) {
        self.log("LocalStorage WARNING : can't find android class LocalStorage. switching to raw localStorage")
      }
      return self.storage.enable();
    },
    //datePicker stuff
    datePicker: {
      init: function(inputs) {

        self.utils.each(inputs, function() {
          var input = this;
          var id = self.utils.attr(input, 'id');

          self.log('datePicker setted with value=' + input.value);
          self.utils.attr(input, 'type', 'text');
          self.datePicker.enhanceFieldValue.apply(input);

          input.addEventListener('focus', function() {
            self.log('show formPicker date for date #', id);
            input.blur();
            var previousDate = self.storage.get('CobaltDatePickerValue_' + id);
            if (!previousDate) {
              var d = new Date();
              previousDate = {
                year: d.getFullYear(),
                day: d.getDate(),
                month: d.getMonth() + 1
              };
            }
            self.send({
              type: "ui",
              control: "picker",
              data: {
                type: "date",
                date: previousDate,
                texts: self.datePicker.texts
              }
            }, function(newDate) {
              if (newDate && newDate.year) {
                input.value = newDate.year + '-' + newDate.month + '-' + newDate.day;
                self.log('setting storage date ', newDate);
                self.storage.set('CobaltDatePickerValue_' + id, newDate);
                self.datePicker.enhanceFieldValue.apply(input);
              } else {
                self.log('removing storage date');
                input.value = "";
                self.storage.remove('CobaltDatePickerValue_' + id);
              }
            });
            return false;

          }, false);

        });
      },
      val: function(input) {
        var date = self.storage.get('CobaltDatePickerValue_' + self.utils.attr(input, 'id'));
        if (date) {
          var str_date = self.datePicker.stringifyDate(date);
          self.log('returning storage date ', str_date);
          return str_date;
        }
        return undefined;
      }
    },

    //default behaviours
    handleCallback: self.defaultBehaviors.handleCallback,
    handleUnknown: self.defaultBehaviors.handleUnknown
  };

  return Adapter;
};
<|endoftext|>


<commit_before>import camelCase from "lodash";
import mongoose from "mongoose";

class ChimeraSchemaError extends Error {
    constructor(message){
        super(message);
        this.name = 'ChimeraSchemaError';
    }
};

class ChimeraSchema extends mongoose.Schema {

    /**
     * Creates a foriegn key association, treating this as the dependent schema of the relationship.
     * @param {string} modelName - The name of the mongoose model to associate.
     * @param {object} [options] - Configuration options for the virtual field declaration.
     * @param {string} [options.localField] - The name of the field in this schema that holds the foreign key reference.localField
     * @param {string} [options.foreignField] - The name of the field on the associated schema that matches the foreign key reference.
     * @param {string} [options.as] - The name to use for the virtual field that describes the association.
     * @param {object} [schemaOptions] - Mongoose schema type options to pass to the localField declaration.
     * @returns {ChimeraSchema} - Mutates the schema by adding a virtual field to describe this association.
     */
    belongsTo(modelName, options = {}, schemaOptions = {}){
        if(!modelName){
            throw new ChimeraSchemaError(`Missing required parameter 'modelName'.`);
        }

        const ref = modelName;

        const localField = options.localField || `${camelCase(modelName)}Id`;
        if(!this.path(localField)){
            this.add({
                [localField]: {
                    ...schemaOptions,
                    type: mongoose.Schema.Types.ObjectId,
                    ref
                }
            });
        }

        const associationName = options.as || camelCase(modelName);
        const association = {
            ref,
            localField,
            foreignField: options.foreignField || '_id',
            justOne: true
        }

        this.virtual(associationName, association);

        return this;
    }

    /**
     * Creates a one-to-many association, treating this as the independent schema of the relationship.
     * @param {string} modelName - The name of the mongoose model to associate.
     * @param {object} options - Configuration options for the virtual field declaration.
     * @param {string} options.localField - The name of the field in this schema that holds the foreign key reference.localField
     * @param {string} [options.foreignField] - The name of the field on the associated schema that matches the foreign key reference.
     * @param {string} [options.as] - The name to use for the virtual field that describes the association.
     * @returns {ChimeraSchema} - Mutates the schema by adding a virtual field to describe this association.
     */
    hasMany(modelName, options){
        if(!modelName){
            throw new ChimeraSchemaError(`Missing required parameter 'modelName'.`);
        }

        if(!options.foreignField){
            throw new ChimeraSchemaError(`Missing required option 'foreignField'.`);
        }

        const ref = modelName;
        const foreignField = options.foreignField;
        const localField = options.localField || '_id';

        const associationName = options.as || `${camelCase(modelName)}Set`;
        const association = {
            ref,
            localField,
            foreignField
        }

        this.virtual(associationName, association);

        return this;
    }

    /**
     * Creates a one-to-one association, treating this as the independent schema of the relationship.
     * @param {string} modelName - The name of the mongoose model to associate.
     * @param {object} options - Configuration options for the virtual field declaration.
     * @param {string} options.localField - The name of the field in this schema that holds the foreign key reference.localField
     * @param {string} [options.foreignField] - The name of the field on the associated schema that matches the foreign key reference.
     * @param {string} [options.as] - The name to use for the virtual field that describes the association.
     * @returns {ChimeraSchema} - Mutates the schema by adding a virtual field to describe this association.
     */
    hasOne(modelName, options){
        if(!modelName){
            throw new ChimeraSchemaError(`Missing required parameter 'modelName'.`);
        }

        if(!options.foreignField){
            throw new ChimeraSchemaError(`Missing required option 'foreignField'.`);
        }

        const ref = modelName;
        const foreignField = options.foreignField;
        const localField = options.localField || `_id`;

        const associationName = options.as || `${camelCase(modelName)}`;
        const association = {
            ref,
            localField,
            foreignField,
            justOne: true
        }

        this.virtual(associationName, association);

        return this;
    }

}

export default ChimeraSchema;<commit_msg>Updated comments about hasOne usage.<commit_after>import camelCase from "lodash";
import mongoose from "mongoose";

class ChimeraSchemaError extends Error {
    constructor(message){
        super(message);
        this.name = 'ChimeraSchemaError';
    }
};

class ChimeraSchema extends mongoose.Schema {

    /**
     * Creates a foriegn key association, treating this as the dependent schema of the relationship.
     * @param {string} modelName - The name of the mongoose model to associate.
     * @param {object} [options] - Configuration options for the virtual field declaration.
     * @param {string} [options.localField] - The name of the field in this schema that holds the foreign key reference.localField
     * @param {string} [options.foreignField] - The name of the field on the associated schema that matches the foreign key reference.
     * @param {string} [options.as] - The name to use for the virtual field that describes the association.
     * @param {object} [schemaOptions] - Mongoose schema type options to pass to the localField declaration.
     * @returns {ChimeraSchema} - Mutates the schema by adding a virtual field to describe this association.
     */
    belongsTo(modelName, options = {}, schemaOptions = {}){
        if(!modelName){
            throw new ChimeraSchemaError(`Missing required parameter 'modelName'.`);
        }

        const ref = modelName;

        const localField = options.localField || `${camelCase(modelName)}Id`;
        if(!this.path(localField)){
            this.add({
                [localField]: {
                    ...schemaOptions,
                    type: mongoose.Schema.Types.ObjectId,
                    ref
                }
            });
        }

        const associationName = options.as || camelCase(modelName);
        const association = {
            ref,
            localField,
            foreignField: options.foreignField || '_id',
            justOne: true
        }

        this.virtual(associationName, association);

        return this;
    }

    /**
     * Creates a one-to-many association, treating this as the independent schema of the relationship.
     * @param {string} modelName - The name of the mongoose model to associate.
     * @param {object} options - Configuration options for the virtual field declaration.
     * @param {string} options.localField - The name of the field in this schema that holds the foreign key reference.localField
     * @param {string} [options.foreignField] - The name of the field on the associated schema that matches the foreign key reference.
     * @param {string} [options.as] - The name to use for the virtual field that describes the association.
     * @returns {ChimeraSchema} - Mutates the schema by adding a virtual field to describe this association.
     */
    hasMany(modelName, options){
        if(!modelName){
            throw new ChimeraSchemaError(`Missing required parameter 'modelName'.`);
        }

        if(!options.foreignField){
            throw new ChimeraSchemaError(`Missing required option 'foreignField'.`);
        }

        const ref = modelName;
        const foreignField = options.foreignField;
        const localField = options.localField || '_id';

        const associationName = options.as || `${camelCase(modelName)}Set`;
        const association = {
            ref,
            localField,
            foreignField
        }

        this.virtual(associationName, association);

        return this;
    }

    /**
     * Creates a one-to-one association, treating this as the independent schema of the relationship.
     * To ensure the integrity of the association, a unique index should also be placed on the on the
     * dependent schema of the relationship to protect a one-to-many relationship from forming.
     * @param {string} modelName - The name of the mongoose model to associate.
     * @param {object} options - Configuration options for the virtual field declaration.
     * @param {string} options.localField - The name of the field in this schema that holds the foreign key reference.localField
     * @param {string} [options.foreignField] - The name of the field on the associated schema that matches the foreign key reference.
     * @param {string} [options.as] - The name to use for the virtual field that describes the association.
     * @returns {ChimeraSchema} - Mutates the schema by adding a virtual field to describe this association.
     */
    hasOne(modelName, options){
        if(!modelName){
            throw new ChimeraSchemaError(`Missing required parameter 'modelName'.`);
        }

        if(!options.foreignField){
            throw new ChimeraSchemaError(`Missing required option 'foreignField'.`);
        }

        const ref = modelName;
        const foreignField = options.foreignField;
        const localField = options.localField || `_id`;

        const associationName = options.as || `${camelCase(modelName)}`;
        const association = {
            ref,
            localField,
            foreignField,
            justOne: true
        }

        this.virtual(associationName, association);

        return this;
    }

}

export default ChimeraSchema;<|endoftext|>


<commit_before>import Aggregate from './Aggregate';
import {ValidAggregateOps} from './util/AggregateOps';
import {accessor, accessorFields, inherits} from 'vega-util';

/**
 * Aggregate and pivot selected field values to become new fields.
 * This operator is useful to construction cross-tabulations.
 * @constructor
 * @param {Array<function(object): *>} [params.groupby] - An array of accessors
 *  to groupby. These fields act just like groupby fields of an Aggregate transform.
 * @param {function(object): *} params.field - The field to pivot on. The unique
 *  values of this field become new field names in the output stream.
 * @param {function(object): *} params.value - The field to populate pivoted fields.
 *  The aggregate values of this field become the values of the new pivoted fields.
 * @param {string} [params.op] - The aggregation operation for the value field,
 *  applied per cell in the output stream. The default is "sum".
 * @param {number} [params.limit] - An optional parameter indicating the maximum
 *  number of pivoted fields to generate. The pivoted field names are sorted in
 *  ascending order prior to enforcing the limit.
 */
export default function Pivot(params) {
  Aggregate.call(this, params);
}

Pivot.Definition = {
  "type": "Pivot",
  "metadata": {"generates": true, "changes": true},
  "params": [
    { "name": "groupby", "type": "field", "array": true },
    { "name": "field", "type": "field", "required": true },
    { "name": "value", "type": "field", "required": true },
    { "name": "op", "type": "enum", "values": ValidAggregateOps, "default": "sum" },
    { "name": "limit", "type": "number", "default": 0 },
    { "name": "key", "type": "field" }
  ]
};

var prototype = inherits(Pivot, Aggregate);

prototype._transform = prototype.transform;

prototype.transform = function(_, pulse) {
  return this._transform(aggregateParams(_, pulse), pulse);
};

// Shoehorn a pivot transform into an aggregate transform!
// First collect all unique pivot field values.
// Then generate aggregate fields for each output pivot field.
function aggregateParams(_, pulse) {
  var key    = _.field,
  value  = _.value,
      op     = (_.op === 'count' ? '__count__' : _.op) || 'sum',
      fields = accessorFields(key).concat(accessorFields(value)),
      keys   = pivotKeys(key, _.limit || 0, pulse);

  return {
    key:      _.key,
    groupby:  _.groupby,
    ops:      keys.map(function() { return op; }),
    fields:   keys.map(function(k) { return get(k, key, value, fields); }),
    as:       keys.map(function(k) { return k + ''; }),
    modified: _.modified.bind(_)
  };
}

// Generate aggregate field accessor.
// Output NaN for non-existent values; aggregator will ignore!
function get(k, key, value, fields) {
  return accessor(
    function(d) { return key(d) === k ? value(d) : NaN; },
    fields,
    k + ''
  );
}

// Collect (and optionally limit) all unique pivot values.
function pivotKeys(key, limit, pulse) {
  var map = {},
      list = [];

  pulse.visit(pulse.SOURCE, function(t) {
    var k = key(t);
    if (!map[k]) {
      map[k] = 1;
      list.push(k);
    }
  });

  // TODO? Move this comparator to vega-util?
  list.sort(function(u, v) {
    return (u<v||u==null) && v!=null ? -1
      : (u>v||v==null) && u!=null ? 1
      : ((v=v instanceof Date?+v:v),(u=u instanceof Date?+u:u))!==u && v===v ? -1
      : v!==v && u===u ? 1 : 0;
  });

  return limit ? list.slice(0, limit) : list;
}
<commit_msg>Fix Pivot state update on data stream change. (#1851)<commit_after>import Aggregate from './Aggregate';
import {ValidAggregateOps} from './util/AggregateOps';
import {accessor, accessorFields, inherits} from 'vega-util';

/**
 * Aggregate and pivot selected field values to become new fields.
 * This operator is useful to construction cross-tabulations.
 * @constructor
 * @param {Array<function(object): *>} [params.groupby] - An array of accessors
 *  to groupby. These fields act just like groupby fields of an Aggregate transform.
 * @param {function(object): *} params.field - The field to pivot on. The unique
 *  values of this field become new field names in the output stream.
 * @param {function(object): *} params.value - The field to populate pivoted fields.
 *  The aggregate values of this field become the values of the new pivoted fields.
 * @param {string} [params.op] - The aggregation operation for the value field,
 *  applied per cell in the output stream. The default is "sum".
 * @param {number} [params.limit] - An optional parameter indicating the maximum
 *  number of pivoted fields to generate. The pivoted field names are sorted in
 *  ascending order prior to enforcing the limit.
 */
export default function Pivot(params) {
  Aggregate.call(this, params);
}

Pivot.Definition = {
  "type": "Pivot",
  "metadata": {"generates": true, "changes": true},
  "params": [
    { "name": "groupby", "type": "field", "array": true },
    { "name": "field", "type": "field", "required": true },
    { "name": "value", "type": "field", "required": true },
    { "name": "op", "type": "enum", "values": ValidAggregateOps, "default": "sum" },
    { "name": "limit", "type": "number", "default": 0 },
    { "name": "key", "type": "field" }
  ]
};

var prototype = inherits(Pivot, Aggregate);

prototype._transform = prototype.transform;

prototype.transform = function(_, pulse) {
  return this._transform(aggregateParams(_, pulse), pulse);
};

// Shoehorn a pivot transform into an aggregate transform!
// First collect all unique pivot field values.
// Then generate aggregate fields for each output pivot field.
function aggregateParams(_, pulse) {
  var key    = _.field,
      value  = _.value,
      op     = (_.op === 'count' ? '__count__' : _.op) || 'sum',
      fields = accessorFields(key).concat(accessorFields(value)),
      keys   = pivotKeys(key, _.limit || 0, pulse);

  // if data stream content changes, pivot fields may change
  // flag parameter modification to ensure re-initialization
  if (pulse.changed()) _.set('__pivot__', null, null, true);

  return {
    key:      _.key,
    groupby:  _.groupby,
    ops:      keys.map(function() { return op; }),
    fields:   keys.map(function(k) { return get(k, key, value, fields); }),
    as:       keys.map(function(k) { return k + ''; }),
    modified: _.modified.bind(_)
  };
}

// Generate aggregate field accessor.
// Output NaN for non-existent values; aggregator will ignore!
function get(k, key, value, fields) {
  return accessor(
    function(d) { return key(d) === k ? value(d) : NaN; },
    fields,
    k + ''
  );
}

// Collect (and optionally limit) all unique pivot values.
function pivotKeys(key, limit, pulse) {
  var map = {},
      list = [];

  pulse.visit(pulse.SOURCE, function(t) {
    var k = key(t);
    if (!map[k]) {
      map[k] = 1;
      list.push(k);
    }
  });

  // TODO? Move this comparator to vega-util?
  list.sort(function(u, v) {
    return (u<v||u==null) && v!=null ? -1
      : (u>v||v==null) && u!=null ? 1
      : ((v=v instanceof Date?+v:v),(u=u instanceof Date?+u:u))!==u && v===v ? -1
      : v!==v && u===u ? 1 : 0;
  });

  return limit ? list.slice(0, limit) : list;
}
<|endoftext|>


<commit_before>
define([
  'jquery',
  'underscore',
  'backbone',
  'collections/sidebar',
  'text!templates/sidebar.html',
  'text!templates/sidebar_nav.html'
], function ($, _, Backbone, SidebarCollection,
             SidebarTemplate, SidebarNavTemplate) {

  var SidebarView = Backbone.View.extend ({
    el: '#sidebar',

    initialize: function () {
      this.sidebarCollection = new SidebarCollection ();

      $("#left-navbar").html (_.template (SidebarNavTemplate));

      // register channels event
      var that = this;
      $("#channels a").click (function () { that.click (this); });

      $("#sidebar-wrapper").on ('scroll', function () {
        that.checkScroll (this);
      });
    },

    render: function () {

      var that = this;

      this.isLoading = true;

      this.sidebarCollection.fetch ({

        success: function (entries) {

          $(that.el).append (_.template (SidebarTemplate,
                                         {entries: entries.toJSON ()}));

          that.isLoading = false;

        }

      });
    },

    click: function (ev) {
      var channel = $(ev).attr ('href').match (/^#(hot)*(\/)*(.*?)$/, '')[3];

      if (channel == '')
        channel = 'populer';

      this.sidebarCollection.channel = channel;
      this.sidebarCollection.page = 1;
      this.sidebarCollection.reset ();
      $(this.el).html ('');
      this.render ();
    },

    checkScroll: function () {
      if (!this.isLoading &&
          !this.sidebarCollection.lastPage () &&
          $('#sidebar-wrapper').scrollTop () +
              $('#sidebar-wrapper').height () + 200 >
              this.el.scrollHeight) {
        this.sidebarCollection.page++;
        this.render ();
      }

    }

  });

  return SidebarView;

});
<commit_msg>add toggle left frame methods<commit_after>
define([
  'jquery',
  'underscore',
  'backbone',
  'collections/sidebar',
  'text!templates/sidebar.html',
  'text!templates/sidebar_nav.html'
], function ($, _, Backbone, SidebarCollection,
             SidebarTemplate, SidebarNavTemplate) {

  var SidebarView = Backbone.View.extend ({
    el: '#sidebar',
    leftFrameVisible: false,

    initialize: function () {
      this.sidebarCollection = new SidebarCollection ();

      $("#left-navbar").html (_.template (SidebarNavTemplate));

      // register channels event
      var that = this;
      $("#channels a").click (function () { that.click (this); });

      $("#sidebar-wrapper").on ('scroll', function () {
        that.checkScroll (this);
      });

      // bind show left frame button for mobile layout
      $('#left-frame-toggle').click (function () { that.toggleLeftFrame (); });

      // bind navbar toggle button
      $('.nav a').click (function () {
        if ($(this).attr ('href') != '#')
          $('#navbar-toggle').click ();
      });
    },

    render: function () {

      var that = this;

      this.isLoading = true;

      this.sidebarCollection.fetch ({

        success: function (entries) {

          $(that.el).append (_.template (SidebarTemplate,
                                         {entries: entries.toJSON ()}));

          that.isLoading = false;

        }

      });
    },

    click: function (ev) {
      var channel = $(ev).attr ('href').match (/^#(hot)*(\/)*(.*?)$/, '')[3];

      if (channel == '')
        channel = 'populer';

      this.sidebarCollection.channel = channel;
      this.sidebarCollection.page = 1;
      this.sidebarCollection.reset ();
      $(this.el).html ('');
      this.render ();
    },

    checkScroll: function () {
      if (!this.isLoading &&
          !this.sidebarCollection.lastPage () &&
          $('#sidebar-wrapper').scrollTop () +
              $('#sidebar-wrapper').height () + 200 >
              this.el.scrollHeight) {
        this.sidebarCollection.page++;
        this.render ();
      }

    },

    showLeftFrame: function () {
      $('#sidebar-wrapper').css ('margin-left', '0');
      this.leftFrameVisible = true;
    },

    hideLeftFrame: function () {
      $('#sidebar-wrapper').css ('margin-left', '-250px');
      this.leftFrameVisible = false;
    },

    toggleLeftFrame: function () {
      if (!this.leftFrameVisible)
        this.showLeftFrame ();
      else
        this.hideLeftFrame ();
    }

  });

  return SidebarView;

});
<|endoftext|>


<commit_before>// Dependencies
import React, { Component } from 'react';
import ProtoController from './ProtoController';
import Card from './Card';
import FileCard from './FileCard';
import StatusView from './StatusView';
import PNGEncoder from './PNGEncoder';
import get from '../../utils/get';

class AdminController extends ProtoController {

    constructor(...args) {
        super(...args);
        this.listFiles = this.listFiles.bind(this);
        this.handleUpload = this.handleUpload.bind(this);
        this.handleDeleteVersion = this.handleDeleteVersion.bind(this);

        this.state = Object.assign({}, this.state, {
            title: 'Admin',
            navigation: [
                ['admin', 'Admin'],
                ['pngencoder', 'PNGEncoder'],
            ],
            files: [],
            status: false,
            needsAuthentication: true,
        });
    }

    appGotAuthenticated() {
        this.listFiles();
    }

    listFiles() {
        get('/documents', 'GET', {}, (err, response) => {
            response = JSON.parse(response);
            if (!response.error) {
                this.setState({
                    files: response,
                });
            }
        });
    }

    handleUpload(event) {
        event.preventDefault();

        // Clear any error message that has happened before
        this.setState({
            status: false,
        });

        // Construct the payload
        const payload = new FormData();
        payload.append('file', event.target[0].files[0]); // file
        payload.append('destination', event.target[1].value); // location selector

        // Upload the file
        get('/documents', 'POST', {
            payload,
            noJSON: true,
            onprogress: (event) => {
                this.setState({
                    status: {
                        text: parseInt((100 / event.total) * event.loaded, 10) + '% uploaded...',
                        type: 'progress',
                    },
                });
            },
        }, (err, response) => {

            if (err) return console.log(err);
            response = JSON.parse(response);

            // Error handling
            if (response.error) {
                return this.setState({
                    status: {
                        text: response.error,
                        type: 'error',
                    },
                });
            }

            // Clear the form and rerender the document list
            this.refs.uploadForm.reset();
            this.setState({
                status: {
                    text: 'Successfully uploaded file!',
                    type: 'success',
                },
            });
            this.listFiles();
        });
    }

    handleDeleteVersion(file, version, event) {
        event.preventDefault();

        const timestamp = file.versions[version].time;
        get('/documents/' + file.filename + '/' + timestamp, 'DELETE', {}, (err, response) => {
            this.listFiles();
        });
    }

    content(navItems, routerParams, routerPath) {
        switch (routerPath.path.slice(1)) {
        case 'admin': {
            return (

                    <Card>
                        # Upload File
                        <StatusView status={this.state.status}></StatusView>
                        <form onSubmit={this.handleUpload} ref="uploadForm">
                            <input type="file" name="file"></input>
                            <select>
                                <option value="versioned_documents">Versioned Documents</option>
                                <option value="public_documents">Public Documents</option>
                            </select>
                            <button type="submit">Upload</button>
                        </form>
                    </Card>
                    {this.state.files.map((file, index) => (
                        <FileCard
                            key={index}
                            file={file}
                            deleteVersion={this.handleDeleteVersion.bind(this, file)}></FileCard>
                    ))}

            );
        }
        case 'pngencoder': {
            return (

                    <Card>
                        # PNGEncoder
                        <PNGEncoder></PNGEncoder>
                    </Card>

            );
        }
        }
    }
}

export default AdminController;
<commit_msg>Link to public documents<commit_after>// Dependencies
import React, { Component } from 'react';
import ProtoController from './ProtoController';
import Card from './Card';
import FileCard from './FileCard';
import StatusView from './StatusView';
import PNGEncoder from './PNGEncoder';
import get from '../../utils/get';

class AdminController extends ProtoController {

    constructor(...args) {
        super(...args);
        this.listFiles = this.listFiles.bind(this);
        this.handleUpload = this.handleUpload.bind(this);
        this.handleDeleteVersion = this.handleDeleteVersion.bind(this);

        this.state = Object.assign({}, this.state, {
            title: 'Admin',
            navigation: [
                ['admin', 'Admin'],
                ['pngencoder', 'PNGEncoder'],
            ],
            files: [],
            status: false,
            needsAuthentication: true,
        });
    }

    appGotAuthenticated() {
        this.listFiles();
    }

    listFiles() {
        get('/documents', 'GET', {}, (err, response) => {
            response = JSON.parse(response);
            if (!response.error) {
                this.setState({
                    files: response,
                });
            }
        });
    }

    handleUpload(event) {
        event.preventDefault();

        // Clear any error message that has happened before
        this.setState({
            status: false,
        });

        // Construct the payload
        const payload = new FormData();
        payload.append('file', event.target[0].files[0]); // file
        payload.append('destination', event.target[1].value); // location selector

        // Upload the file
        get('/documents', 'POST', {
            payload,
            noJSON: true,
            onprogress: (event) => {
                this.setState({
                    status: {
                        text: parseInt((100 / event.total) * event.loaded, 10) + '% uploaded...',
                        type: 'progress',
                    },
                });
            },
        }, (err, response) => {

            if (err) return console.log(err);
            response = JSON.parse(response);

            // Error handling
            if (response.error) {
                return this.setState({
                    status: {
                        text: response.error,
                        type: 'error',
                    },
                });
            }

            // Clear the form and rerender the document list
            this.refs.uploadForm.reset();
            this.setState({
                status: {
                    text: 'Successfully uploaded file!',
                    type: 'success',
                },
            });
            this.listFiles();
        });
    }

    handleDeleteVersion(file, version, event) {
        event.preventDefault();

        const timestamp = file.versions[version].time;
        get('/documents/' + file.filename + '/' + timestamp, 'DELETE', {}, (err, response) => {
            this.listFiles();
        });
    }

    content(navItems, routerParams, routerPath) {
        switch (routerPath.path.slice(1)) {
        case 'admin': {
            return (

                    <Card>
                        # Upload File
                        <StatusView status={this.state.status}></StatusView>
                        <form onSubmit={this.handleUpload} ref="uploadForm">
                            <input type="file" name="file"></input>
                            <select>
                                <option value="versioned_documents">Versioned Documents</option>
                                <option value="public_documents">Public Documents</option>
                            </select>
                            <button type="submit">Upload</button>
                        </form>
                        Public documents
                    </Card>
                    {this.state.files.map((file, index) => (
                        <FileCard
                            key={index}
                            file={file}
                            deleteVersion={this.handleDeleteVersion.bind(this, file)}></FileCard>
                    ))}

            );
        }
        case 'pngencoder': {
            return (

                    <Card>
                        # PNGEncoder
                        <PNGEncoder></PNGEncoder>
                    </Card>

            );
        }
        }
    }
}

export default AdminController;
<|endoftext|>


<commit_before>import { withStyles } from '@material-ui/core/styles'
import debounce from 'lodash/debounce'

const styles = theme => ({
  imageContainer: {
    position: 'relative',
    left: '50%',
    transform: 'translateX(-50%)',
  },
  image: {
    width: '100%',
    height: '100%'
  }
})

const ceiling = (num, ceiling) => (num > ceiling) ? ceiling : num

const byRatio = (initial, ratio) => Math.round(initial * ratio)

const maxWidth = () => global.innerWidth

const maxHeight = topOffset => global.innerHeight - topOffset

class FullScreenPhoto extends React.Component {
  constructor(props) {
    super(props)
    this.image = React.createRef()
    this.state = {
      isClient: false,
      imageLoaded: false,
    }
  }

  componentDidMount() {
    window.addEventListener('resize', this.handleResize, false)
    this.setState({ ...this.state, isClient: true })

    const element = this.image.current
     if (element && element.complete) {
      this.setState({ isClient: true, imageLoaded: true })
     }
  }

  componentDidUpdate({ photo }) {
    if (this.props.photo !== photo) {
      this.setState({ ...this.state, imageLoaded: false })
    }
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize, false)
  }

  handleResize = debounce(() => this.forceUpdate(), 20, false)

  handleImageLoad = _event => {
    this.setState({ ...this.state, imageLoaded: true })
  }

  calculateImageDimensions() {
    const { photo, topOffset } = this.props
    if (!this.state.isClient) {
      return { display: 'none', }
    }
    const { width, height } = photo.versions.desktop
    if (this.heightRatio(topOffset) > this.widthRatio()) {
      return {
        width: byRatio(width, this.widthRatio()),
        height: byRatio(height, this.widthRatio()),
      }
    }
    return {
      width: byRatio(width, this.heightRatio(topOffset)),
      height: byRatio(height, this.heightRatio(topOffset)),
    }
  }

  showPhoto() {
    if (!this.state.isClient || !this.state.imageLoaded) {
      return { display: 'none', }
    }
    return { display: 'block', }
  }

  heightRatio(topOffset) {
    return ceiling(maxHeight(topOffset) / this.props.photo.versions.desktop.height, 1)
  }

  widthRatio() {
    return ceiling(maxWidth() / this.props.photo.versions.desktop.width, 1)
  }

  render() {
    const { photo, classes } = this.props
    const { urls, versions } = photo

    return (
      <div
        className={classes.imageContainer}
        style={{ ...this.calculateImageDimensions() }}>
        <img
          style={{ ...this.showPhoto() }}
          className={classes.image}
          ref={this.image}
          onLoad={this.handleImageLoad}
          src={photo.urls.desktop}
          srcSet={Object.keys(versions).map(
            key => `${versions[key].url} ${versions[key].width}w`
          ).join(', ')}
          alt={photo.alt} />
      </div>
    )
  }
}

export default withStyles(styles)(FullScreenPhoto)
<commit_msg>Show photo while it's loading<commit_after>import { withStyles } from '@material-ui/core/styles'
import debounce from 'lodash/debounce'

const styles = theme => ({
  imageContainer: {
    position: 'relative',
    left: '50%',
    transform: 'translateX(-50%)',
  },
  image: {
    width: '100%',
    height: '100%'
  }
})

const ceiling = (num, ceiling) => (num > ceiling) ? ceiling : num

const byRatio = (initial, ratio) => Math.round(initial * ratio)

const maxWidth = () => global.innerWidth

const maxHeight = topOffset => global.innerHeight - topOffset

class FullScreenPhoto extends React.Component {
  constructor(props) {
    super(props)
    this.image = React.createRef()
    this.state = {
      isClient: false,
      showImage: true,
    }
  }

  componentDidMount() {
    window.addEventListener('resize', this.handleResize, false)
    this.setState({ ...this.state, isClient: true })

    const element = this.image.current
     if (element && element.complete) {
      this.setState({ isClient: true, showImage: true })
     }
  }

  componentDidUpdate({ photo }) {
    if (this.props.photo !== photo) {
      this.setState({ ...this.state, showImage: false })
      global.setTimeout(this.handleTimeout, 20)
    }
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize, false)
  }

  handleResize = debounce(() => this.forceUpdate(), 20, false)

  handleTimeout = _event => {
    this.setState({ ...this.state, showImage: true })
  }

  calculateImageDimensions() {
    const { photo, topOffset } = this.props
    if (!this.state.isClient) {
      return { display: 'none', }
    }
    const { width, height } = photo.versions.desktop
    if (this.heightRatio(topOffset) > this.widthRatio()) {
      return {
        width: byRatio(width, this.widthRatio()),
        height: byRatio(height, this.widthRatio()),
      }
    }
    return {
      width: byRatio(width, this.heightRatio(topOffset)),
      height: byRatio(height, this.heightRatio(topOffset)),
    }
  }

  showPhoto() {
    if (!this.state.isClient || !this.state.showImage) {
      return { display: 'none', }
    }
    return { display: 'block', }
  }

  heightRatio(topOffset) {
    return ceiling(maxHeight(topOffset) / this.props.photo.versions.desktop.height, 1)
  }

  widthRatio() {
    return ceiling(maxWidth() / this.props.photo.versions.desktop.width, 1)
  }

  render() {
    const { photo, classes } = this.props
    const { urls, versions } = photo

    return (
      <div
        className={classes.imageContainer}
        style={{ ...this.calculateImageDimensions() }}>
        <img
          style={{ ...this.showPhoto() }}
          className={classes.image}
          ref={this.image}
          src={photo.urls.desktop}
          srcSet={Object.keys(versions).map(
            key => `${versions[key].url} ${versions[key].width}w`
          ).join(', ')}
          alt={photo.alt} />
      </div>
    )
  }
}

export default withStyles(styles)(FullScreenPhoto)
<|endoftext|>


<commit_before>// DecentCMS (c) 2014 Bertrand Le Roy, under MIT. See LICENSE.txt for licensing details.
'use strict';

// TODO: enable global require for non-DecentCMS dependencies, so we can load them only once, save on memory and ramp-up times. Should be fine for common libraries such as async.
// TODO: maybe, inject scope by property rather than constructor, to remove the requirement for service implementers to implement a constructor with scope as the parameter.

var async = require('async');
var util = require('util');

/**
 * @description
 * Constructs an instance of the class passed in.
 * If the class is static, the same object
 * is always returned.
 * Otherwise, a new instance is created on each call.
 * Don't call this directly, it should only be internally used by scope methods.
 * @param {Function|object} ServiceClass The class to instantiate.
 *  This constructor must always take a scope as its first argument.
 *  It can also take an optional 'options' argument, unless it's a shell singleton.
 * @param {object} options Options to pass into the service's constructor.
 * @returns {object} An instance of the service, or null if it wasn't found.
 */
function construct(scope, ServiceClass, options) {
  return ServiceClass ?
      ServiceClass.isStatic || typeof ServiceClass !== 'function' ?
    ServiceClass : new ServiceClass(scope, options)
    : null;
};

/**
 * @description
 * Gets the instance for a singleton service.
 * This should not be called, except by scope methods.
 * @param {object} scope         The scoped object.
 * @param {string} service       The service name.
 * @param {number} index         The index at which the service is to be cached.
 * @param {object} options       The options to pass into the service constructor.
 * @returns {object} The singleton instance.
 */
function getSingleton(scope, service, index, options) {
  var serviceClasses = scope.services[service];
  // Get or build the instance cache
  var instances = scope.instances[service];
  if (!instances) {
    instances = scope.instances[service] = new Array(serviceClasses.length);
  }
  // Try to get the instance from the cache
  var instance = instances[index];
  if (instance) return instance;
  // if scope is not current, walk parent scopes to find the current one
  var serviceClass = serviceClasses[index];
  var currentScope = scope;
  while (currentScope && (currentScope.scopeName !== serviceClass.scope)) {
    currentScope = currentScope.parentScope;
  }
  // Note: currentScope might be null at this point, which will make the object
  // scope-less. Not necessarily a problem, but probably not very useful.
  if (currentScope && scope != currentScope) {
    var currentScopeInstances = currentScope.instances[service];
    var currentScopeServiceClasses = currentScope.services[service];
    var currentScopeIndex = currentScopeServiceClasses.indexOf(serviceClass);
    if (currentScopeIndex !== -1) {
      if (!currentScopeInstances) {
        currentScopeInstances = currentScope.instances[service] =
          new Array(currentScopeServiceClasses.length);
      }
      instance = currentScopeInstances[currentScopeIndex];
      if (instance) {
        return instances[index] = instance;
      }
      return currentScopeInstances[currentScopeIndex]
        = instances[index]
        = construct(currentScope, serviceClass, options);
    }
  }
  // At this point, if the scope is not currentScope, it's an error case:
  // the service's scope is incompatible with the scope chain or the available
  // services on the scopes.
  if (scope.scopeName !== serviceClass.scope) {
    throw new Error(
      util.format(
        "Couldn't find an instance of %s on scope %s.",
        service, serviceClass.scope));
  }
  return instances[index] = construct(scope, serviceClass, options);
}

/**
 * @description
 * Initializes a service by calling its init method, and wiring up
 * its static events.
 * @param ServiceClass
 */
function initializeService(scope, ServiceClass) {
  if (!ServiceClass) return;
  if (ServiceClass.init) {
    ServiceClass.init(scope);
  }
  // Wire up declared static event handlers
  if (ServiceClass.on) {
    Object.keys(ServiceClass.on).forEach(function forEachEvent(eventName) {
      scope.on(eventName, function(payload) {
        ServiceClass.on[eventName](scope, payload);
      })
    });
  }
}

/**
 * @description
 * Transforms an object into a scope. The role of a scope is to manage the lifecycle
 * of required services. This mixin adds initialize, register, require, getServices,
 * and tearDown methods that can be used to get service instances that are scoped
 * to the object, live and die with it.
 * @mixin
 * @param {string} name          The name of the scope.
 * @param {object} objectToScope The object that must be made a scope.
 * @param {object} services      A map of the services to be made available from require.
 * @param {object} [parentScope] An optional parent scope that may have valid instances of services to hand down.
 */
function scope(name, objectToScope, services, parentScope) {
  if (services) {
    objectToScope.services = services;
  }
  objectToScope.scopeName = name;
  objectToScope.parentScope = parentScope;

  /**
   * @description
   * Initialize services for this scope. This is called automatically if the scope was built
   * with a set of services. Otherwise, it must be called manually.
   */
  objectToScope.initialize = function() {
    if (this.services) {
      // Initialize all services except those that are scoped to some other scope
      for (var serviceName in this.services) {
        var serviceClasses = this.services[serviceName];
        if (!serviceClasses) continue;
        for (var i = 0; i < serviceClasses.length; i++) {
          var ServiceClass = serviceClasses[i];
          if (!ServiceClass.scope || ServiceClass.scope === this.scopeName) {
            initializeService(this, ServiceClass);
          }
        }
      }
      this._scopeInitialized = true;
    }
  };

  /**
   * @description
   * Registers a service into the scope's registry, making it available for require and
   * getServices. This will initialize the service if the scope is already initialized.
   * @param {string} name     The service name implemented by ServiceClass.
   * @param {Function|object} ServiceClass The service constructor,
   *                          or the static service object to register.
   */
  objectToScope.register = function(name, ServiceClass) {
    if (!this.services[name]) {
      this.services[name] = [ServiceClass];
    }
    else {
      this.services[name].push(ServiceClass);
    }
    if (this._scopeInitialized) {
      // Scope has already initialized its services, so any new one that gets added
      // must also be initialized.
      if (!ServiceClass.scope || ServiceClass.scope === objectToScope.scopeName) {
        initializeService(objectToScope, ServiceClass);
      }
    }
  };

  /**
   * @description
   * Returns an instance of a service implementing the named contract passed as a parameter.
   * If more than one service exists for that contract, one instance that
   * has dependencies on any other service for that contract is returned. Do not
   * count on any particular service being returned if that is the case among the ones
   * that have the most dependencies.
   * A new instance is returned every time the function is called, unless the service
   * is static, or if it is a scope singleton.
   *
   * @param {String} service  The name of the contract for which a service instance is required.
   * @param {object} options Options to pass into the service's constructor.
   * @returns {object} An instance of the service, or null if it wasn't found.
   */
  objectToScope.require = function require(service, options) {
    var services = this.services[service];
    var ServiceClass = services && services.length > 0 ?
      services[services.length - 1] : null;
    if (!ServiceClass) return null;
    if (ServiceClass.isScopeSingleton) {
      return getSingleton(this, service, services.length - 1, options);
    }
    return construct(this, ServiceClass, options);
  };

  /**
   * @description
   * Returns a list of service instances that are implementing the named contract passed as a parameter.
   * The services are returned in order of dependency: if service A has a dependency
   * on service B, B is guaranteed to appear earlier in the list.
   * New instances are returned every time the function is called.
   *
   * @param {String} service The name of the contract for which service instances are required.
   * @param {object} options Options to pass into the services' constructors.
   * @returns {Array} An array of instances of the service.
   */
  objectToScope.getServices = function getServices(service, options) {
    var self = this;
    if (!(service in self.services)) return [];
    return self.services[service].map(function(ServiceClass, index) {
      if (ServiceClass.isScopeSingleton) {
        return getSingleton(self, service, index, options);
      }
      return construct(self, ServiceClass, options);
    });
  };

  /**
   * @description
   * Calls a method on each registered service of the specified name,
   * asynchronously.
   * @param {string} service The name of the service.
   * @param {string} method  The name of the method.
   * @param {object} options The parameter to pass to the method.
   * @param {Function} done  The function to call when all service methods have returned.
   */
  objectToScope.callService = function callService(service, method, options, done) {
    async.eachSeries(
      this.getServices(service),
      function callMethod(service, next) {
        service[method](options, next);
      },
      done
    );
  };

  /**
   * @description
   * Creates a lifecycle function that calls into all the
   * service methods specified in an alternated list of
   * service names, and method names as parameters.
   * It is possible to replace service/method pairs with
   * a function(options, done) that will be called as part
   * of the lifecycle execution.
   *
   * For example:
   *
   *   scope.lifecycle(
   *     'service1', 'methodA',
   *     'service2', 'methodB',
   *     function(options, done) {...},
   *     'service3', 'methodC'
   *   )
   *
   * returns a function that will call methodA on all instances
   * of service1, then methodB on all instances of service2,
   * then the function, then methodC on all instances of service3.
   *
   * @param {string} service The service name.
   * @param {string} method  The method name.
   * @returns {Function} A function that takes an options object and a callback as a parameter.
   */
  objectToScope.lifecycle = function lifecycle(service, method) {
    var methodArray = [];
    for (var i = 0; i < arguments.length; i++) {
      var serviceName = arguments[i];
      if (typeof(serviceName) === 'function') {
        methodArray.push(serviceName);
        continue;
      }
      var services = this.getServices(serviceName);
      var methodName = arguments[++i];
      Array.prototype.push.apply(
        methodArray,
        services.map(function serviceToMethod(service) {
          return service[methodName].bind(service);
        })
      );
    }
    return function lifecycle(options, done) {
      async.applyEachSeries(methodArray, options, done);
    };
  };

  objectToScope.instances = {};
  if (services) {
    objectToScope.initialize();
  }

  return objectToScope;
}

module.exports = scope;<commit_msg>Fix scope doc comment to reflect current set of mixin methods.<commit_after>// DecentCMS (c) 2014 Bertrand Le Roy, under MIT. See LICENSE.txt for licensing details.
'use strict';

// TODO: enable global require for non-DecentCMS dependencies, so we can load them only once, save on memory and ramp-up times. Should be fine for common libraries such as async.
// TODO: maybe, inject scope by property rather than constructor, to remove the requirement for service implementers to implement a constructor with scope as the parameter.

var async = require('async');
var util = require('util');

/**
 * @description
 * Constructs an instance of the class passed in.
 * If the class is static, the same object
 * is always returned.
 * Otherwise, a new instance is created on each call.
 * Don't call this directly, it should only be internally used by scope methods.
 * @param {Function|object} ServiceClass The class to instantiate.
 *  This constructor must always take a scope as its first argument.
 *  It can also take an optional 'options' argument, unless it's a shell singleton.
 * @param {object} options Options to pass into the service's constructor.
 * @returns {object} An instance of the service, or null if it wasn't found.
 */
function construct(scope, ServiceClass, options) {
  return ServiceClass ?
      ServiceClass.isStatic || typeof ServiceClass !== 'function' ?
    ServiceClass : new ServiceClass(scope, options)
    : null;
};

/**
 * @description
 * Gets the instance for a singleton service.
 * This should not be called, except by scope methods.
 * @param {object} scope         The scoped object.
 * @param {string} service       The service name.
 * @param {number} index         The index at which the service is to be cached.
 * @param {object} options       The options to pass into the service constructor.
 * @returns {object} The singleton instance.
 */
function getSingleton(scope, service, index, options) {
  var serviceClasses = scope.services[service];
  // Get or build the instance cache
  var instances = scope.instances[service];
  if (!instances) {
    instances = scope.instances[service] = new Array(serviceClasses.length);
  }
  // Try to get the instance from the cache
  var instance = instances[index];
  if (instance) return instance;
  // if scope is not current, walk parent scopes to find the current one
  var serviceClass = serviceClasses[index];
  var currentScope = scope;
  while (currentScope && (currentScope.scopeName !== serviceClass.scope)) {
    currentScope = currentScope.parentScope;
  }
  // Note: currentScope might be null at this point, which will make the object
  // scope-less. Not necessarily a problem, but probably not very useful.
  if (currentScope && scope != currentScope) {
    var currentScopeInstances = currentScope.instances[service];
    var currentScopeServiceClasses = currentScope.services[service];
    var currentScopeIndex = currentScopeServiceClasses.indexOf(serviceClass);
    if (currentScopeIndex !== -1) {
      if (!currentScopeInstances) {
        currentScopeInstances = currentScope.instances[service] =
          new Array(currentScopeServiceClasses.length);
      }
      instance = currentScopeInstances[currentScopeIndex];
      if (instance) {
        return instances[index] = instance;
      }
      return currentScopeInstances[currentScopeIndex]
        = instances[index]
        = construct(currentScope, serviceClass, options);
    }
  }
  // At this point, if the scope is not currentScope, it's an error case:
  // the service's scope is incompatible with the scope chain or the available
  // services on the scopes.
  if (scope.scopeName !== serviceClass.scope) {
    throw new Error(
      util.format(
        "Couldn't find an instance of %s on scope %s.",
        service, serviceClass.scope));
  }
  return instances[index] = construct(scope, serviceClass, options);
}

/**
 * @description
 * Initializes a service by calling its init method, and wiring up
 * its static events.
 * @param ServiceClass
 */
function initializeService(scope, ServiceClass) {
  if (!ServiceClass) return;
  if (ServiceClass.init) {
    ServiceClass.init(scope);
  }
  // Wire up declared static event handlers
  if (ServiceClass.on) {
    Object.keys(ServiceClass.on).forEach(function forEachEvent(eventName) {
      scope.on(eventName, function(payload) {
        ServiceClass.on[eventName](scope, payload);
      })
    });
  }
}

/**
 * @description
 * Transforms an object into a scope. The role of a scope is to manage the lifecycle
 * of required services. This mixin adds initialize, register, require, getServices,
 * callService, and lifecycle methods that can be used to get service instances
 * that are scoped to the object, live and die with it.
 * @mixin
 * @param {string} name          The name of the scope.
 * @param {object} objectToScope The object that must be made a scope.
 * @param {object} services      A map of the services to be made available from require.
 * @param {object} [parentScope] An optional parent scope that may have valid instances of services to hand down.
 */
function scope(name, objectToScope, services, parentScope) {
  if (services) {
    objectToScope.services = services;
  }
  objectToScope.scopeName = name;
  objectToScope.parentScope = parentScope;

  /**
   * @description
   * Initialize services for this scope. This is called automatically if the scope was built
   * with a set of services. Otherwise, it must be called manually.
   */
  objectToScope.initialize = function() {
    if (this.services) {
      // Initialize all services except those that are scoped to some other scope
      for (var serviceName in this.services) {
        var serviceClasses = this.services[serviceName];
        if (!serviceClasses) continue;
        for (var i = 0; i < serviceClasses.length; i++) {
          var ServiceClass = serviceClasses[i];
          if (!ServiceClass.scope || ServiceClass.scope === this.scopeName) {
            initializeService(this, ServiceClass);
          }
        }
      }
      this._scopeInitialized = true;
    }
  };

  /**
   * @description
   * Registers a service into the scope's registry, making it available for require and
   * getServices. This will initialize the service if the scope is already initialized.
   * @param {string} name     The service name implemented by ServiceClass.
   * @param {Function|object} ServiceClass The service constructor,
   *                          or the static service object to register.
   */
  objectToScope.register = function(name, ServiceClass) {
    if (!this.services[name]) {
      this.services[name] = [ServiceClass];
    }
    else {
      this.services[name].push(ServiceClass);
    }
    if (this._scopeInitialized) {
      // Scope has already initialized its services, so any new one that gets added
      // must also be initialized.
      if (!ServiceClass.scope || ServiceClass.scope === objectToScope.scopeName) {
        initializeService(objectToScope, ServiceClass);
      }
    }
  };

  /**
   * @description
   * Returns an instance of a service implementing the named contract passed as a parameter.
   * If more than one service exists for that contract, one instance that
   * has dependencies on any other service for that contract is returned. Do not
   * count on any particular service being returned if that is the case among the ones
   * that have the most dependencies.
   * A new instance is returned every time the function is called, unless the service
   * is static, or if it is a scope singleton.
   *
   * @param {String} service  The name of the contract for which a service instance is required.
   * @param {object} options Options to pass into the service's constructor.
   * @returns {object} An instance of the service, or null if it wasn't found.
   */
  objectToScope.require = function require(service, options) {
    var services = this.services[service];
    var ServiceClass = services && services.length > 0 ?
      services[services.length - 1] : null;
    if (!ServiceClass) return null;
    if (ServiceClass.isScopeSingleton) {
      return getSingleton(this, service, services.length - 1, options);
    }
    return construct(this, ServiceClass, options);
  };

  /**
   * @description
   * Returns a list of service instances that are implementing the named contract passed as a parameter.
   * The services are returned in order of dependency: if service A has a dependency
   * on service B, B is guaranteed to appear earlier in the list.
   * New instances are returned every time the function is called.
   *
   * @param {String} service The name of the contract for which service instances are required.
   * @param {object} options Options to pass into the services' constructors.
   * @returns {Array} An array of instances of the service.
   */
  objectToScope.getServices = function getServices(service, options) {
    var self = this;
    if (!(service in self.services)) return [];
    return self.services[service].map(function(ServiceClass, index) {
      if (ServiceClass.isScopeSingleton) {
        return getSingleton(self, service, index, options);
      }
      return construct(self, ServiceClass, options);
    });
  };

  /**
   * @description
   * Calls a method on each registered service of the specified name,
   * asynchronously.
   * @param {string} service The name of the service.
   * @param {string} method  The name of the method.
   * @param {object} options The parameter to pass to the method.
   * @param {Function} done  The function to call when all service methods have returned.
   */
  objectToScope.callService = function callService(service, method, options, done) {
    async.eachSeries(
      this.getServices(service),
      function callMethod(service, next) {
        service[method](options, next);
      },
      done
    );
  };

  /**
   * @description
   * Creates a lifecycle function that calls into all the
   * service methods specified in an alternated list of
   * service names, and method names as parameters.
   * It is possible to replace service/method pairs with
   * a function(options, done) that will be called as part
   * of the lifecycle execution.
   *
   * For example:
   *
   *   scope.lifecycle(
   *     'service1', 'methodA',
   *     'service2', 'methodB',
   *     function(options, done) {...},
   *     'service3', 'methodC'
   *   )
   *
   * returns a function that will call methodA on all instances
   * of service1, then methodB on all instances of service2,
   * then the function, then methodC on all instances of service3.
   *
   * @param {string} service The service name.
   * @param {string} method  The method name.
   * @returns {Function} A function that takes an options object and a callback as a parameter.
   */
  objectToScope.lifecycle = function lifecycle(service, method) {
    var methodArray = [];
    for (var i = 0; i < arguments.length; i++) {
      var serviceName = arguments[i];
      if (typeof(serviceName) === 'function') {
        methodArray.push(serviceName);
        continue;
      }
      var services = this.getServices(serviceName);
      var methodName = arguments[++i];
      Array.prototype.push.apply(
        methodArray,
        services.map(function serviceToMethod(service) {
          return service[methodName].bind(service);
        })
      );
    }
    return function lifecycle(options, done) {
      async.applyEachSeries(methodArray, options, done);
    };
  };

  objectToScope.instances = {};
  if (services) {
    objectToScope.initialize();
  }

  return objectToScope;
}

module.exports = scope;<|endoftext|>


<commit_before>const template = require('./template.hbs');

module.exports = ($) => {
    if (window && window.chrome && window.chrome.webstore) {
        return;
    }

    const div = $(template());
    $('body').append(div);
};
<commit_msg>Fixing Chrome detection.<commit_after>const template = require('./template.hbs');

module.exports = ($) => {
    if (window
            && window.chrome
            && (window.chrome.webstore || window.chrome.runtime)
            ) {
        return;
    }

    const div = $(template());
    $('body').append(div);
};
<|endoftext|>


<commit_before>var matchComment = /^\s*\/\/.*$/gm;

module.exports = function (text) {
    if (!text) {
        return {
            mappings: [],
            replacements: []
        };
    }

    // Strip comments
    text = text.replace(matchComment, "");

    var config = JSON.parse(text);
    if (config.version !== 1) {
        throw new Error("Unsupported config version: " + config.version);
    }

    return {
        mappings: config.mappings.map(function (mapping) {
            var match = parseValue(mapping.match);
            var path = parseValue(mapping.path);

            return function (url) {
                if (url.search(match) !== -1) {
                    return url.replace(match, path);
                }
            };
        }),
        replacements: config.replacements.map(function (replacement) {
            var match = parseValue(replacement.match);
            // by default `replace` doesn't globally replace plain strings,
            // so wrap it in a regex with the global flag set
            if (typeof match === "string") {
                match = new RegExp(escapeStringForRegExp(match), "g");
            }
            var isMatchFn = typeof match == "function";
            var replace = parseValue(replacement.replace);
            var isReplaceFn = typeof replace == "function";

            return function (content, context) {
                var _match = isMatchFn ? match(context) : match;
                var _replace = isReplaceFn ? replace(context) : replace;
                return content.replace(_match, _replace);
            };
        })
    };
};

function parseValue(value) {
    if (typeof value === "object") {
        if (value.regex) {
            // global regex by default
            return new RegExp(value.regex, value.flags || "g");
        } else if (value.var) {
            var path = value.var;
            return function (context) {
                return getPath(context, path);
            };
        }
    } else {
        return value;
    }
}

var headDotTail = /^([^\.]+)(\.(.*))?$/;
function getPath(context, path) {
    if (!context || !path) {
        return undefined;
    }

    var parts = headDotTail.exec(path);
    var head = parts[1];
    var tail = parts[3];
    if (!tail) {
        return context[head];
    } else if (context[head]) {
        return getPath(context[head], tail);
    }
    return undefined;
}

// from http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escapeStringForRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}<commit_msg>Handle config files with unknown value types<commit_after>var matchComment = /^\s*\/\/.*$/gm;

module.exports = function (text) {
    if (!text) {
        return {
            mappings: [],
            replacements: []
        };
    }

    // Strip comments
    text = text.replace(matchComment, "");

    var config = JSON.parse(text);
    if (config.version !== 1) {
        throw new Error("Unsupported config version: " + config.version);
    }

    return {
        mappings: config.mappings.map(function (mapping) {
            var match = parseValue(mapping.match);
            var path = parseValue(mapping.path);

            return function (url) {
                if (url.search(match) !== -1) {
                    return url.replace(match, path);
                }
            };
        }),
        replacements: config.replacements.map(function (replacement) {
            var match = parseValue(replacement.match);
            // by default `replace` doesn't globally replace plain strings,
            // so wrap it in a regex with the global flag set
            if (typeof match === "string") {
                match = new RegExp(escapeStringForRegExp(match), "g");
            }
            var isMatchFn = typeof match == "function";
            var replace = parseValue(replacement.replace);
            var isReplaceFn = typeof replace == "function";

            return function (content, context) {
                var _match = isMatchFn ? match(context) : match;
                var _replace = isReplaceFn ? replace(context) : replace;
                return content.replace(_match, _replace);
            };
        })
    };
};

function parseValue(value) {
    if (typeof value === "object") {
        if (value.regex) {
            // global regex by default
            return new RegExp(value.regex, value.flags || "g");
        } else if (value.var) {
            var path = value.var;
            return function (context) {
                return getPath(context, path);
            };
        } else {
            throw new Error("Unknown value: " + JSON.stringify(value));
        }
    } else {
        return value;
    }
}

var headDotTail = /^([^\.]+)(\.(.*))?$/;
function getPath(context, path) {
    if (!context || !path) {
        return undefined;
    }

    var parts = headDotTail.exec(path);
    var head = parts[1];
    var tail = parts[3];
    if (!tail) {
        return context[head];
    } else if (context[head]) {
        return getPath(context[head], tail);
    }
    return undefined;
}

// from http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escapeStringForRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}<|endoftext|>


<commit_before>/**
 * Topic.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/documentation/concepts/models-and-orm/models
 */

var _ = require('lodash');
var Promise = require('bluebird');

module.exports = {

  attributes: {
    name: {
      type: Sequelize.STRING,
      allowNull: false
    },

    title: {
      type: Sequelize.TEXT,
      allowNull: false
    },

    description: {
      type: Sequelize.TEXT,
    },

    usage: {
      type: Sequelize.TEXT,
    },

    details: {
      type: Sequelize.TEXT,
    },

    value: {
      type: Sequelize.TEXT,
    },

    references: {
      type: Sequelize.TEXT,
    },

    note: {
      type: Sequelize.TEXT,
    },

    author: {
      type: Sequelize.TEXT,
    },

    seealso: {
      type: Sequelize.TEXT,
    },

    examples: {
      type: Sequelize.TEXT,
    },

    sourceJSON: {
      type: Sequelize.TEXT,
      allowNull: true
    }

  },
  associations: function() {

    Topic.belongsTo(PackageVersion,
      {
        as: 'package_version',
        foreignKey: {
          allowNull: false,
          required: true,
          name: 'package_version_id',
          as: 'package_version'
        }
      });

    Topic.hasMany(Argument, {as: 'arguments', foreignKey: 'topic_id'});

    Topic.hasMany(Section, {as: 'sections', foreignKey: 'topic_id'});

    Topic.hasMany(Alias, {as: 'aliases', foreignKey: 'topic_id'});

    Topic.hasMany(Review, {
      as: 'reviews',
      foreignKey: 'reviewable_id',
      constraints: false,
      scope: {
        reviewable: 'topic'
      }
    });

    Topic.hasMany(Example, { as: 'user_examples', foreignKey: 'topic_id' });

    Topic.belongsToMany(Tag, {as: 'keywords', through: 'TopicTags', foreignKey: 'topic_id', timestamps: false});
  },

  options: {
    underscored: true,

    indexes: [
      {
        type: 'UNIQUE',
        fields: ['name', 'package_version_id']
      }
    ],

    getterMethods: {
      uri: function()  {
        if (this.package_version) {
          return '/api/packages/:name/versions/:version/topics/:topic'
            .replace(':name', encodeURIComponent(this.package_version.package_name))
            .replace(':version', encodeURIComponent(this.package_version.version))
            .replace(':topic', encodeURIComponent(this.name))
            .replace('/api/', '/');
        } else return sails.getUrlFor({ target: 'Topic.findById' })
          .replace(':id', encodeURIComponent(this.id))
          .replace('/api/', '/');
      },
      api_uri: function()  {
        if (this.package_version) {
          return '/api/packages/:name/versions/:version/topics/:topic'
            .replace(':name', encodeURIComponent(this.package_version.package_name))
            .replace(':version', encodeURIComponent(this.package_version.version))
            .replace(':topic', encodeURIComponent(this.name));
        } else return sails.getUrlFor({ target: 'Topic.findById' })
          .replace(':id', encodeURIComponent(this.id));
      },
    },

    classMethods: {
      findOnePopulated: function(criteria, opts) {
        function customizer(objValue, srcValue) {
          if (_.isArray(objValue)) {
            return objValue.concat(srcValue);
          }
        }
        var options = _.mergeWith({
          where: criteria,
          include: [
            {model: Argument, as: 'arguments', attributes: ['name', 'description', 'topic_id'], separate:true },
            {model: Section, as: 'sections', attributes: ['name', 'description', 'topic_id'], separate:true },
            {model: Tag, as: 'keywords', attributes: ['name']},
            {model: Alias, as: 'aliases', attributes: ['name', 'topic_id'], separate: true },
            {model: Example, as: 'user_examples',
              include: [{model: User, as: 'user', attributes: ['username']}]
            },
            { model: PackageVersion,
              as: 'package_version',
              attributes: ['package_name', 'version']
            }
          ]
        }, opts, customizer);

        return Topic.findOne(options);
      },

      findByNameInPackage: function(packageName, name, version) {
        var packageCriteria = {
          package_name: packageName
        };

        if(version) { //if version is specified, find in this one
          packageCriteria.version = version;
        }

        var params = {
          where: {
            name: name
          },
          include: [
            { model: PackageVersion, as: 'package_version', attributes: ['package_name', 'version'], where: packageCriteria }
          ]
        };

        return Topic.findAll(params).then(function(topics) {
          return topics.sort(PackageService.compareVersions('desc', function(topic) {
            return topic.package_version.version })
          )[0];
        });
      },

      findByAliasInPackage: function(packageName, alias, version) {
        var packageCriteria = {
          package_name: packageName
        };

        if(version) { //if version is specified, find in this one
          packageCriteria.version = version;
        }

        var params = {
          include: [
            { model: PackageVersion, as: 'package_version', attributes: ['package_name', 'version'], where: packageCriteria },
            { model: Alias, as: 'aliases', attributes: ['name', 'topic_id'], where: { name: alias }}
          ]
        };

        return Topic.findAll(params).then(function(topicInstances) {
          if(topicInstances) return topicInstances.sort(PackageService.compareVersions('desc', function(topic) {
            return topic.package_version.version })
          )[0];
          else return topicInstances;
        });
      },

      createWithRdFile: function(opts) {
        var rdJSON = opts.input;
        return sequelize.transaction(function (t) {
          var attributes = [
            'name',
            'title',
            'description',
            'usage',
            'details',
            'value',
            'references',
            'note',
            'seealso',
            'examples'
          ];

          var topic = _.pick(rdJSON, attributes);

          var arrayToString = function(val) {
            if (val instanceof Array) {
              if(_.isEmpty(val)) return "";
              else return val.join(" ");
            } else return val;
          };

          topic = _.mapValues(topic, arrayToString);

          var customSections = rdJSON.sections;

          customSections = _.map(customSections, function(section) {
            return _.mapValues(section, arrayToString);
          });

          topic.sourceJSON = JSON.stringify(rdJSON);

          var packageVersion = {
            package_name: opts.packageName,
            version: opts.packageVersion,
            description: "",
            license: ""
          };

          return PackageVersion.upsertPackageVersion(packageVersion, {
            transaction: t
          }).spread(function(version, created) {

            topic.package_version_id = version.id;

            var keywords = rdJSON.keywords && !(rdJSON.keywords instanceof Array) ? [rdJSON.keywords] : rdJSON.keywords;
            var keywordsRecords =  _.isEmpty(keywords) ? [] :
              _.chain(keywords)
                .map(function(entry) { return entry.split(','); })
                .flatten()
                .map(function(keyword) {
                  return {name: keyword};
                })
                .value();

            return Promise.join(
              Topic.findOrCreate({
                where: {
                  package_version_id: version.id,
                  name: topic.name
                },
                defaults: topic,
                transaction: t,
                include: [
                  {model: Tag, as: 'keywords'},
                  {model: Argument, as: 'arguments'},
                  {model: Section, as: 'sections'},
                  {model: Alias, as: 'aliases'}
                ]
              }),
              Tag.bulkCreate(keywordsRecords, {transaction:t, ignoreDuplicates: true})
              .then(function(instances) {
                var names = _.map(instances, function (inst) {
                  return inst.name;
                });
                return Tag.findAll({where: {name: {$in: names}}, transaction:t });
              }),
              function(instanceCreatedArray, keywordsInstances) {
                var topicInstance = instanceCreatedArray[0];
                topicInstance.set(_.pick(topic, ['title',
                  'description',
                  'usage',
                  'details',
                  'value',
                  'references',
                  'note',
                  'seealso',
                  'examples',
                  'author',
                  'sourceJSON'])
                );

                var topicArguments = _.isEmpty(rdJSON.arguments) ? [] : rdJSON.arguments.map(function(argument) {
                  var arg = _.mapValues(argument, arrayToString);
                  return _.merge({}, arg, {topic_id: topicInstance.id});
                });

                var aliases = rdJSON.aliases && !(rdJSON.aliases instanceof Array) ? [rdJSON.aliases] : rdJSON.aliases;
                var aliasesRecords = _.isEmpty(aliases) ? [] : aliases.map(function(alias) {
                  return {name: alias, topic_id: topicInstance.id};
                });

                var sections = customSections.map(function(section) {
                  var sect = _.mapValues(section, arrayToString);
                  return { name: sect.title, description: sect.contents, topic_id: topicInstance.id };
                });

                return Promise.all([
                  topicInstance.removeArguments(topicInstance.arguments),
                  topicInstance.removeSections(topicInstance.sections),
                  topicInstance.removeAliases(topicInstance.aliases),
                  Argument.bulkCreate(topicArguments, {transaction: t}),
                  Alias.bulkCreate(aliasesRecords, {transaction: t}),
                  Section.bulkCreate(sections, {transaction: t}),
                  topicInstance.setKeywords(keywordsInstances, {transaction: t }),
                  topicInstance.save({transaction: t})
                ]).then(_.partial(Topic.findOnePopulated, {id: topicInstance.id}, {transaction: t}));
            });
          });

        });

      }
    }
  }

};

<commit_msg>also search in aliases<commit_after>/**
 * Topic.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/documentation/concepts/models-and-orm/models
 */

var _ = require('lodash');
var Promise = require('bluebird');

module.exports = {

  attributes: {
    name: {
      type: Sequelize.STRING,
      allowNull: false
    },

    title: {
      type: Sequelize.TEXT,
      allowNull: false
    },

    description: {
      type: Sequelize.TEXT,
    },

    usage: {
      type: Sequelize.TEXT,
    },

    details: {
      type: Sequelize.TEXT,
    },

    value: {
      type: Sequelize.TEXT,
    },

    references: {
      type: Sequelize.TEXT,
    },

    note: {
      type: Sequelize.TEXT,
    },

    author: {
      type: Sequelize.TEXT,
    },

    seealso: {
      type: Sequelize.TEXT,
    },

    examples: {
      type: Sequelize.TEXT,
    },

    sourceJSON: {
      type: Sequelize.TEXT,
      allowNull: true
    }

  },
  associations: function() {

    Topic.belongsTo(PackageVersion,
      {
        as: 'package_version',
        foreignKey: {
          allowNull: false,
          required: true,
          name: 'package_version_id',
          as: 'package_version'
        }
      });

    Topic.hasMany(Argument, {as: 'arguments', foreignKey: 'topic_id'});

    Topic.hasMany(Section, {as: 'sections', foreignKey: 'topic_id'});

    Topic.hasMany(Alias, {as: 'aliases', foreignKey: 'topic_id'});

    Topic.hasMany(Review, {
      as: 'reviews',
      foreignKey: 'reviewable_id',
      constraints: false,
      scope: {
        reviewable: 'topic'
      }
    });

    Topic.hasMany(Example, { as: 'user_examples', foreignKey: 'topic_id' });

    Topic.belongsToMany(Tag, {as: 'keywords', through: 'TopicTags', foreignKey: 'topic_id', timestamps: false});
  },

  options: {
    underscored: true,

    indexes: [
      {
        type: 'UNIQUE',
        fields: ['name', 'package_version_id']
      }
    ],

    getterMethods: {
      uri: function()  {
        if (this.package_version) {
          return '/api/packages/:name/versions/:version/topics/:topic'
            .replace(':name', encodeURIComponent(this.package_version.package_name))
            .replace(':version', encodeURIComponent(this.package_version.version))
            .replace(':topic', encodeURIComponent(this.name))
            .replace('/api/', '/');
        } else return sails.getUrlFor({ target: 'Topic.findById' })
          .replace(':id', encodeURIComponent(this.id))
          .replace('/api/', '/');
      },
      api_uri: function()  {
        if (this.package_version) {
          return '/api/packages/:name/versions/:version/topics/:topic'
            .replace(':name', encodeURIComponent(this.package_version.package_name))
            .replace(':version', encodeURIComponent(this.package_version.version))
            .replace(':topic', encodeURIComponent(this.name));
        } else return sails.getUrlFor({ target: 'Topic.findById' })
          .replace(':id', encodeURIComponent(this.id));
      },
    },

    classMethods: {
      findOnePopulated: function(criteria, opts) {
        function customizer(objValue, srcValue) {
          if (_.isArray(objValue)) {
            return objValue.concat(srcValue);
          }
        }
        var options = _.mergeWith({
          where: criteria,
          include: [
            {model: Argument, as: 'arguments', attributes: ['name', 'description', 'topic_id'], separate:true },
            {model: Section, as: 'sections', attributes: ['name', 'description', 'topic_id'], separate:true },
            {model: Tag, as: 'keywords', attributes: ['name']},
            {model: Alias, as: 'aliases', attributes: ['name', 'topic_id'], separate: true },
            {model: Example, as: 'user_examples',
              include: [{model: User, as: 'user', attributes: ['username']}]
            },
            { model: PackageVersion,
              as: 'package_version',
              attributes: ['package_name', 'version']
            }
          ]
        }, opts, customizer);

        return Topic.findOne(options);
      },

      findByNameInPackage: function(packageName, name, version) {
        var packageCriteria = {
          package_name: packageName
        };

        if(version) { //if version is specified, find in this one
          packageCriteria.version = version;
        }

        var params = {
          include: [
            { model: PackageVersion, as: 'package_version', attributes: ['package_name', 'version'], where: packageCriteria },
            { model: Alias, as: 'aliases', attributes: ['name', 'topic_id'], required: false }
          ],
          where: {
            $or: [{ name: name }, { '$`aliases`.`name`$': name }]
          },
        };

        return Topic.findAll(params).then(function(topics) {
          return topics.sort(PackageService.compareVersions('desc', function(topic) {
            return topic.package_version.version })
          )[0];
        });
      },

      findByAliasInPackage: function(packageName, alias, version) {
        var packageCriteria = {
          package_name: packageName
        };

        if(version) { //if version is specified, find in this one
          packageCriteria.version = version;
        }

        var params = {
          include: [
            { model: PackageVersion, as: 'package_version', attributes: ['package_name', 'version'], where: packageCriteria },
            { model: Alias, as: 'aliases', attributes: ['name', 'topic_id'], where: { name: alias }}
          ]
        };

        return Topic.findAll(params).then(function(topicInstances) {
          if(topicInstances) return topicInstances.sort(PackageService.compareVersions('desc', function(topic) {
            return topic.package_version.version })
          )[0];
          else return topicInstances;
        });
      },

      createWithRdFile: function(opts) {
        var rdJSON = opts.input;
        return sequelize.transaction(function (t) {
          var attributes = [
            'name',
            'title',
            'description',
            'usage',
            'details',
            'value',
            'references',
            'note',
            'seealso',
            'examples'
          ];

          var topic = _.pick(rdJSON, attributes);

          var arrayToString = function(val) {
            if (val instanceof Array) {
              if(_.isEmpty(val)) return "";
              else return val.join(" ");
            } else return val;
          };

          topic = _.mapValues(topic, arrayToString);

          var customSections = rdJSON.sections;

          customSections = _.map(customSections, function(section) {
            return _.mapValues(section, arrayToString);
          });

          topic.sourceJSON = JSON.stringify(rdJSON);

          var packageVersion = {
            package_name: opts.packageName,
            version: opts.packageVersion,
            description: "",
            license: ""
          };

          return PackageVersion.upsertPackageVersion(packageVersion, {
            transaction: t
          }).spread(function(version, created) {

            topic.package_version_id = version.id;

            var keywords = rdJSON.keywords && !(rdJSON.keywords instanceof Array) ? [rdJSON.keywords] : rdJSON.keywords;
            var keywordsRecords =  _.isEmpty(keywords) ? [] :
              _.chain(keywords)
                .map(function(entry) { return entry.split(','); })
                .flatten()
                .map(function(keyword) {
                  return {name: keyword};
                })
                .value();

            return Promise.join(
              Topic.findOrCreate({
                where: {
                  package_version_id: version.id,
                  name: topic.name
                },
                defaults: topic,
                transaction: t,
                include: [
                  {model: Tag, as: 'keywords'},
                  {model: Argument, as: 'arguments'},
                  {model: Section, as: 'sections'},
                  {model: Alias, as: 'aliases'}
                ]
              }),
              Tag.bulkCreate(keywordsRecords, {transaction:t, ignoreDuplicates: true})
              .then(function(instances) {
                var names = _.map(instances, function (inst) {
                  return inst.name;
                });
                return Tag.findAll({where: {name: {$in: names}}, transaction:t });
              }),
              function(instanceCreatedArray, keywordsInstances) {
                var topicInstance = instanceCreatedArray[0];
                topicInstance.set(_.pick(topic, ['title',
                  'description',
                  'usage',
                  'details',
                  'value',
                  'references',
                  'note',
                  'seealso',
                  'examples',
                  'author',
                  'sourceJSON'])
                );

                var topicArguments = _.isEmpty(rdJSON.arguments) ? [] : rdJSON.arguments.map(function(argument) {
                  var arg = _.mapValues(argument, arrayToString);
                  return _.merge({}, arg, {topic_id: topicInstance.id});
                });

                var aliases = rdJSON.aliases && !(rdJSON.aliases instanceof Array) ? [rdJSON.aliases] : rdJSON.aliases;
                var aliasesRecords = _.isEmpty(aliases) ? [] : aliases.map(function(alias) {
                  return {name: alias, topic_id: topicInstance.id};
                });

                var sections = customSections.map(function(section) {
                  var sect = _.mapValues(section, arrayToString);
                  return { name: sect.title, description: sect.contents, topic_id: topicInstance.id };
                });

                return Promise.all([
                  topicInstance.removeArguments(topicInstance.arguments),
                  topicInstance.removeSections(topicInstance.sections),
                  topicInstance.removeAliases(topicInstance.aliases),
                  Argument.bulkCreate(topicArguments, {transaction: t}),
                  Alias.bulkCreate(aliasesRecords, {transaction: t}),
                  Section.bulkCreate(sections, {transaction: t}),
                  topicInstance.setKeywords(keywordsInstances, {transaction: t }),
                  topicInstance.save({transaction: t})
                ]).then(_.partial(Topic.findOnePopulated, {id: topicInstance.id}, {transaction: t}));
            });
          });

        });

      }
    }
  }

};

<|endoftext|>