<commit_before>
/**
  @class

  `XT.Model` is an abstract class designed to operate with `XT.DataSource`.
  It should be subclassed for any specific implentation. Subtypes should
  include a `recordType` the data source will use to retreive the record.

  To create a new model include `isNew` in the options like so:
    XM.Contact = XT.Model.extend({recordType: 'XM.Contact'});
    m = new XM.Contact({firstName: 'Randy'}, {isNew: true});

  To load an existing record include a guid in the options like so:
    XM.Contact = XT.Model.extend({recordType: 'XM.Contact'});
    m = new XM.Contact({guid: 1});
    m.fetch();

    OR

    XM.Contact = XT.Model.extend({recordType: 'XM.Contact'});
    m = XM.Contact();
    m.fetch({id: 1});

  @extends Backbone.RelationalModel
  @param {Object} attributes
  @param {Object} options
*/
XT.Model = Backbone.RelationalModel.extend(
  /** @scope XT.Model.prototype */ {

  idAttribute: "guid",

  /**
  Set to true if you want an id fetched from the server when the `isNew` option
  is passed on a new model.

  @type {Boolean}
  */
  autoFetchId: true,

  /**
  Specify the name of a data source model here.

  @type {String}
  */
  recordType: null,

  /**
  A hash structure that defines data access.

  @property
  @type {Hash}
  */
  privileges: {},

  /**
  An array of required attributes. A validate on the entire model will fail
  until all the required attributes have values.

  @type {Array}
  */
  required: [],

  /**
  An array of attribute names designating attributes that are not editable.
  Use `setReadOnly` to edit this array.

  @seealso `setReadOnly`
  @seealso `isReadOnly`
  @type {Array}
  */
  readOnly: [],

  // ..........................................................
  // METHODS
  //

  /**
    Returns whether the current record can be updated based on privilege
    settings.

    @returns {Boolean}
  */
  canUpdate: function() {
    var obj = Backbone.Relational.store.getObjectByName(this.recordType);
    return obj.canUpdate(this);
  },

  /**
    Returns whether the current record can be deleted based on privilege
    settings.

    @returns {Boolean}
  */
  canDelete: function() {
    var obj = Backbone.Relational.store.getObjectByName(this.recordType);
    return obj.canDelete(this);
  },

  /**
  Set the entire model, or a specific model attribute to readOnly. Privilege
  enforcement supercedes read-only settings.

  Examples:

  model.setReadOnly() // sets model to read only
  model.setReadOnly(false) // sets model to be editable
  model.setReadOnly('name') // sets 'name' attribute to read-only
  model.setReadOnly('name', false) // sets 'name' attribute to be editable

  @seealso `isReadOnly`
  @seealso `readOnly`
  @param {String|Boolean} Attribute to set, or boolean if setting the model
  @param {Boolean} boolean - default = true.
  */
  setReadOnly: function(key, value) {
    // handle attribute
    if (_.isString(key)) {
      value = _.isBoolean(value) ? value : true;
      if (value && !_.contains(this.readOnly, key)) {
        this.readOnly.push(key);
      } else if (!value && _.contains(this.readOnly, key)) {
        this.readOnly = _.without(this.readOnly, key);
      }
      return;
    }

    // handle model
    key = _.isBoolean(key) ? key : true;
    this._readOnly = key;
  },

  /**
  Return whether the model is in a read-only state. If an attribute name
  is passed, returns whether that attribute is read-only.

  @seealso `setReadOnly`
  @seealso `readOnly`
  @param {String} attribute
  */
  isReadOnly: function(attr) {
    var canNotUpdate = !this.canUpdate();
    if (!_.isString(attr) || this._readOnly || canNotUpdate) {
      return this._readOnly || canNotUpdate;
    }
    return _.contains(this.readOnly, attr);
  },

  initialize: function() {
    var options = arguments[1];

    // set data source
    this._dataSource = XT.dataSource;

    // initialize for new record
    if (options && options.isNew) {
      this.attributes.dataState = 'create';
      if (this.autoFetchId) this._fetchId();
    }

    // set id to read only
    if (this.idAttribute && !_.contains(this.readOnly, this.idAttribute)) {
      this.readOnly.push(this.idAttribute);
    }

    // set id as required
    if (this.idAttribute && !_.contains(this.required, this.idAttribute)) {
      this.required.push(this.idAttribute);
    }

    // set up state change handler
    this.on('change', this._changed);
  },

  /**
  Reimplemented. A model is new if the dataState is `create`.
  */
  isNew: function() {
    return this.get('dataState') === 'create';
  },

  /**
  Returns true when dataState is `create` or `update`.
  */
  isDirty: function() {
    var dataState = this.get('dataState');
    return dataState === 'create' || dataState === 'update';
  },

  /**
  Reimplemented to handle state change.
  */
  fetch: function(options) {
    options = options ? _.clone(options) : {};
    var that = this;
    var success = options.success;

    // flip sync flag back on success
    options.success = function(resp, status, xhr) {
      that._sync = false;
      that._attrCache = _.clone(that.attributes);
      if (success) success(model, resp, options);
    };

    return Backbone.Model.prototype.fetch.call(this, options);
  },

  /**
  Reimplemented to handle state change.
  */
  save: function(key, value, options) {
    // Handle both `("key", value)` and `({key: value})` -style calls.
    if (_.isObject(key) || key === null || key === undefined) options = value;
    options = options ? _.clone(options) : {};

    var that = this;
    var success = options.success;

    // flip sync flag back on success
    options.success = function(resp, status, xhr) {
      that._sync = false;
      that._attrCache = _.clone(that.attributes);
      if (success) success(model, resp, options);
    };

    // Handle both `("key", value)` and `({key: value})` -style calls.
    if (_.isObject(key) || key === null || key === undefined) value = options;

    return Backbone.Model.prototype.save.call(this, key, value, options);
  },

  /**
  Reimplemented.
  */
  destroy: function(options) {
    options = options ? _.clone(options) : {};
    var that = this;
    var success = options.success;

    // clear on success
    options.success = function(resp, status, xhr) {
      that.clear({silent: true});
      that._attrCache = null;
      if (success) success(model, resp, options);
    };

    this.set('dataState', 'delete');
    return Backbone.Model.prototype.destroy.call(this, options);
  },

  /**
  Overload: sync to xtuple datasource.
  */
  sync: function(method, model, options) {
    var recordType = model.recordType;
    var id = options.id || this.id;
    var success = options.success;

    if (options === undefined) options = {};
    this._sync = true;

    // read
    if (method === 'read' && recordType && id && success) {
      return this._dataSource.retrieveRecord(recordType, id, options);

    // write
    } else if (method === 'create' || method === 'update' || method === 'delete') {
      var ret = this._dataSource.commitRecord(model, options);
      this.set('dataState', 'busy');
      return ret;
    }

    return false;
  },

  /**
  Default validation checks state, required fields and read-only.
  Reimplement your own custom validation code here, but make sure
  to call back to the superclass function using:

  return XT.Model.prototype.validate.call(this, attributes, options);

  @param {Object} attributes
  @param {Object} options
  */
  validate: function(attributes, options) {
    var prop;
    var val;
    var attr;

    //check state
    if (this.get('dataState') === 'busy' && !this._sync) {
      return "Record is busy";
    }

    //check required
    if (!attributes) {
      for (i = 0; i < this.required.length; i++) {
        if (!this.has(this.required[i])) {
          return "'" + this.required[i] + "' is required.";
        }
      }
    }

    //check read-only
    if (attributes) {
      if (this.isReadOnly()) {
        return "Record is in a read only state.";
      } else {
        for (attr in attributes) {
          if (this.isReadOnly(attr)) {
            return "Can not edit read only attribute " + attr + ".";
          }
        }
      }
    }
  },

  // ..........................................................
  // PRIVATE
  //

  /** @private */
  _attrCache: null,

  /** @private */
  _dataSource: null,

  /** @private */
  _readOnly: false,

  /** @private */
  _sync: false,

  /** @private */
  _changed: function() {
    if (this.get('dataState') === 'read' && !this._sync) {
      this.set('dataState', 'update');
    }
  },

  /** @private */
  _fetchId: function() {
    var that = this;
    var options = {};

    // callback
    options.success = function(resp, status, xhr) {
      var attr = that.idAttribute;

      // set the id
      that.setReadOnly(attr, false);
      that.set(attr, resp);
      that.setReadOnly(attr, true);
    };

    // fetch id
    this._dataSource.dispatch('XT.Record', 'fetchId', this.recordType, options);
  }

});

// Class Methods
enyo.mixin( /** @scope XT.Model */ XT.Model, {

  /**
    Use this function to find out whether a user can create records before
    instantiating one.

    @returns {Boolean}
  */
  canCreate: function() {
    var privileges = this.prototype.privileges,
        sessionPrivs = XT.session.getPrivileges(),
        isGranted = false;

    if (sessionPrivs && sessionPrivs.get) {
      // check global
      isGranted = privileges.all && privileges.all.create &&
                  sessionPrivs.get(privileges.all.create) === true;

      // check personal
      if (!isGranted) {
        isGranted = privileges.personal && privileges.personal.create &&
                    sessionPrivs.get(privileges.personal.create) === true;
      }
    }

    return isGranted;
  },

  /**
    Use this function to find out whether a user can read this record type
    before any have been loaded.

    @returns {Boolean}
  */
  canRead: function() {
    var privileges = this.prototype.privileges,
        sessionPrivs = XT.session.privileges,
        isGranted = false;

    if (sessionPrivs && sessionPrivs.get) {
      // check global read privilege
      isGranted = privileges.all && privileges.all.read &&
                  sessionPrivs.get(privileges.all.read) === true;

      // check global update privilege
      if (!isGranted) {
        isGranted = privileges.all && privileges.all.update &&
                    sessionPrivs.get(privileges.all.update) === true;
      }

      // check personal view privilege
      if (!isGranted) {
        isGranted = privileges.personal && privileges.personal.read &&
                    sessionPrivs.get(privileges.personal.read) === true;
      }

      // check personal update privilege
      if (!isGranted) {
        isGranted = privileges.personal && privileges.personal.update &&
                    sessionPrivs.get(privileges.personal.update) === true;
      }
    }

    return isGranted;
  },

  /**
    Returns whether a user has access to update a record of this type. If a
    record is passed that involves personal privileges, it will validate
    whether that particular record is updatable.

    @returns {Boolean}
  */
  canUpdate: function(record) {
    return XT.Model._canDo.call(this, 'update', record);
  },

  /**
    Returns whether a user has access to delete a record of this type. If a
    record is passed that involves personal privileges, it will validate
    whether that particular record is deletable.

    @returns {Boolean}
  */
  canDelete: function(record) {
    return XT.Model._canDo.call(this, 'delete', record);
  },

  /** @private */
  _canDo: function(action, record) {
    var privileges = this.prototype.privileges,
        sessionPrivs = XT.session.privileges,
        isGrantedAll = false,
        isGrantedPersonal = false,
        userName = XT.session.details.username;

    if (sessionPrivs && sessionPrivs.get) {
      // check global privileges
      if (privileges.all && privileges.all[action]) {
        isGrantedAll = sessionPrivs.get(privileges.all[action]) === true;
      }

      // check personal privileges
      if (!isGrantedAll && privileges.personal && privileges.personal[action]) {
        isGrantedPersonal = sessionPrivs.get(privileges.personal[action]) === true;
      }
    }

    // If only personal privileges, check the original attribute cache against
    // the personal attribute list to see if we can update
    if (!isGrantedAll && isGrantedPersonal) {
      var i = 0;
      var username = XT.session.details.username;
      var props = privileges.personal && privileges.personal.properties ?
                  privileges.personal.properties : [];

      isGrantedPersonal = false;
      if (record && record._attrCache) {
        while (!isGrantedPersonal && i < props.length) {
          isGrantedPersonal = m._attrCache[props[i]].get('username') === username;
          i++;
        }
      }
    }

    return isGrantedAll || isGrantedPersonal;
  }

});

<commit_msg>Allow sync to always write.<commit_after>
/**
  @class

  `XT.Model` is an abstract class designed to operate with `XT.DataSource`.
  It should be subclassed for any specific implentation. Subtypes should
  include a `recordType` the data source will use to retreive the record.

  To create a new model include `isNew` in the options like so:
    XM.Contact = XT.Model.extend({recordType: 'XM.Contact'});
    m = new XM.Contact({firstName: 'Randy'}, {isNew: true});

  To load an existing record include a guid in the options like so:
    XM.Contact = XT.Model.extend({recordType: 'XM.Contact'});
    m = new XM.Contact({guid: 1});
    m.fetch();

    OR

    XM.Contact = XT.Model.extend({recordType: 'XM.Contact'});
    m = XM.Contact();
    m.fetch({id: 1});

  @extends Backbone.RelationalModel
  @param {Object} attributes
  @param {Object} options
*/
XT.Model = Backbone.RelationalModel.extend(
  /** @scope XT.Model.prototype */ {

  idAttribute: "guid",

  /**
  Set to true if you want an id fetched from the server when the `isNew` option
  is passed on a new model.

  @type {Boolean}
  */
  autoFetchId: true,

  /**
  Specify the name of a data source model here.

  @type {String}
  */
  recordType: null,

  /**
  A hash structure that defines data access.

  @property
  @type {Hash}
  */
  privileges: {},

  /**
  An array of required attributes. A validate on the entire model will fail
  until all the required attributes have values.

  @type {Array}
  */
  required: [],

  /**
  An array of attribute names designating attributes that are not editable.
  Use `setReadOnly` to edit this array.

  @seealso `setReadOnly`
  @seealso `isReadOnly`
  @type {Array}
  */
  readOnly: [],

  // ..........................................................
  // METHODS
  //

  /**
    Returns whether the current record can be updated based on privilege
    settings.

    @returns {Boolean}
  */
  canUpdate: function() {
    var obj = Backbone.Relational.store.getObjectByName(this.recordType);
    return obj.canUpdate(this);
  },

  /**
    Returns whether the current record can be deleted based on privilege
    settings.

    @returns {Boolean}
  */
  canDelete: function() {
    var obj = Backbone.Relational.store.getObjectByName(this.recordType);
    return obj.canDelete(this);
  },

  /**
  Set the entire model, or a specific model attribute to readOnly. Privilege
  enforcement supercedes read-only settings.

  Examples:

  model.setReadOnly() // sets model to read only
  model.setReadOnly(false) // sets model to be editable
  model.setReadOnly('name') // sets 'name' attribute to read-only
  model.setReadOnly('name', false) // sets 'name' attribute to be editable

  @seealso `isReadOnly`
  @seealso `readOnly`
  @param {String|Boolean} Attribute to set, or boolean if setting the model
  @param {Boolean} boolean - default = true.
  */
  setReadOnly: function(key, value) {
    // handle attribute
    if (_.isString(key)) {
      value = _.isBoolean(value) ? value : true;
      if (value && !_.contains(this.readOnly, key)) {
        this.readOnly.push(key);
      } else if (!value && _.contains(this.readOnly, key)) {
        this.readOnly = _.without(this.readOnly, key);
      }
      return;
    }

    // handle model
    key = _.isBoolean(key) ? key : true;
    this._readOnly = key;
  },

  /**
  Return whether the model is in a read-only state. If an attribute name
  is passed, returns whether that attribute is read-only.

  @seealso `setReadOnly`
  @seealso `readOnly`
  @param {String} attribute
  */
  isReadOnly: function(attr) {
    var canNotUpdate = !this.canUpdate();
    if (!_.isString(attr) || this._readOnly || canNotUpdate) {
      return this._readOnly || canNotUpdate;
    }
    return _.contains(this.readOnly, attr);
  },

  initialize: function() {
    var options = arguments[1];

    // set data source
    this._dataSource = XT.dataSource;

    // initialize for new record
    if (options && options.isNew) {
      this.attributes.dataState = 'create';
      if (this.autoFetchId) this._fetchId();
    }

    // set id to read only
    if (this.idAttribute && !_.contains(this.readOnly, this.idAttribute)) {
      this.readOnly.push(this.idAttribute);
    }

    // set id as required
    if (this.idAttribute && !_.contains(this.required, this.idAttribute)) {
      this.required.push(this.idAttribute);
    }

    // set up state change handler
    this.on('change', this._changed);
  },

  /**
  Reimplemented. A model is new if the dataState is `create`.
  */
  isNew: function() {
    return this.get('dataState') === 'create';
  },

  /**
  Returns true when dataState is `create` or `update`.
  */
  isDirty: function() {
    var dataState = this.get('dataState');
    return dataState === 'create' || dataState === 'update';
  },

  /**
  Reimplemented to handle state change.
  */
  fetch: function(options) {
    options = options ? _.clone(options) : {};
    var that = this;
    var success = options.success;

    // flip sync flag back on success
    options.success = function(resp, status, xhr) {
      that._sync = false;
      that._attrCache = _.clone(that.attributes);
      if (success) success(model, resp, options);
    };

    return Backbone.Model.prototype.fetch.call(this, options);
  },

  /**
  Reimplemented to handle state change.
  */
  save: function(key, value, options) {
    // Handle both `("key", value)` and `({key: value})` -style calls.
    if (_.isObject(key) || key === null || key === undefined) options = value;
    options = options ? _.clone(options) : {};

    var that = this;
    var success = options.success;

    // flip sync flag back on success
    options.success = function(resp, status, xhr) {
      that._sync = false;
      that._attrCache = _.clone(that.attributes);
      if (success) success(model, resp, options);
    };

    // Handle both `("key", value)` and `({key: value})` -style calls.
    if (_.isObject(key) || key === null || key === undefined) value = options;

    return Backbone.Model.prototype.save.call(this, key, value, options);
  },

  /**
  Reimplemented.
  */
  destroy: function(options) {
    options = options ? _.clone(options) : {};
    var that = this;
    var success = options.success;

    // clear on success
    options.success = function(resp, status, xhr) {
      that.clear({silent: true});
      that._attrCache = null;
      if (success) success(model, resp, options);
    };

    this.set('dataState', 'delete');
    return Backbone.Model.prototype.destroy.call(this, options);
  },

  /**
  Overload: sync to xtuple datasource.
  */
  sync: function(method, model, options) {
    var recordType = model.recordType;
    var id = options.id || this.id;
    var success = options.success;

    if (options === undefined) options = {};
    this._sync = true;

    // read
    if (method === 'read' && recordType && id && success) {
      return this._dataSource.retrieveRecord(recordType, id, options);

    // write
    } else if (method === 'create' || method === 'update' || method === 'delete') {
      var ret = this._dataSource.commitRecord(model, options);
      this.set('dataState', 'busy');
      return ret;
    }

    return false;
  },

  /**
  Default validation checks required fields and read-only.
  Reimplement your own custom validation code here, but make sure
  to call back to the superclass at the top of your function using:

  return XT.Model.prototype.validate.call(this, attributes, options);

  @param {Object} attributes
  @param {Object} options
  */
  validate: function(attributes, options) {
    // if we're syncing, bail out
    if (this._sync) return;

    var prop;
    var val;
    var attr;

    //check required
    if (!attributes) {
      for (i = 0; i < this.required.length; i++) {
        if (!this.has(this.required[i])) {
          return "'" + this.required[i] + "' is required.";
        }
      }
    }

    //check read-only
    if (attributes) {
      if (this.isReadOnly()) {
        return "Record is in a read only state.";
      } else {
        for (attr in attributes) {
          if (this.isReadOnly(attr)) {
            return "Can not edit read only attribute " + attr + ".";
          }
        }
      }
    }
  },

  // ..........................................................
  // PRIVATE
  //

  /** @private */
  _attrCache: null,

  /** @private */
  _dataSource: null,

  /** @private */
  _readOnly: false,

  /** @private */
  _sync: false,

  /** @private */
  _changed: function() {
    if (this.get('dataState') === 'read' && !this._sync) {
      this.set('dataState', 'update');
    }
  },

  /** @private */
  _fetchId: function() {
    var that = this;
    var options = {};

    // callback
    options.success = function(resp, status, xhr) {
      var attr = that.idAttribute;

      // set the id
      that.setReadOnly(attr, false);
      that.set(attr, resp);
      that.setReadOnly(attr, true);
    };

    // fetch id
    this._dataSource.dispatch('XT.Record', 'fetchId', this.recordType, options);
  }

});

// ..........................................................
// CLASS METHODS
//

enyo.mixin( /** @scope XT.Model */ XT.Model, {

  /**
    Use this function to find out whether a user can create records before
    instantiating one.

    @returns {Boolean}
  */
  canCreate: function() {
    var privileges = this.prototype.privileges,
        sessionPrivs = XT.session.getPrivileges(),
        isGranted = false;

    if (sessionPrivs && sessionPrivs.get) {
      // check global
      isGranted = privileges.all && privileges.all.create &&
                  sessionPrivs.get(privileges.all.create) === true;

      // check personal
      if (!isGranted) {
        isGranted = privileges.personal && privileges.personal.create &&
                    sessionPrivs.get(privileges.personal.create) === true;
      }
    }

    return isGranted;
  },

  /**
    Use this function to find out whether a user can read this record type
    before any have been loaded.

    @returns {Boolean}
  */
  canRead: function() {
    var privileges = this.prototype.privileges,
        sessionPrivs = XT.session.privileges,
        isGranted = false;

    if (sessionPrivs && sessionPrivs.get) {
      // check global read privilege
      isGranted = privileges.all && privileges.all.read &&
                  sessionPrivs.get(privileges.all.read) === true;

      // check global update privilege
      if (!isGranted) {
        isGranted = privileges.all && privileges.all.update &&
                    sessionPrivs.get(privileges.all.update) === true;
      }

      // check personal view privilege
      if (!isGranted) {
        isGranted = privileges.personal && privileges.personal.read &&
                    sessionPrivs.get(privileges.personal.read) === true;
      }

      // check personal update privilege
      if (!isGranted) {
        isGranted = privileges.personal && privileges.personal.update &&
                    sessionPrivs.get(privileges.personal.update) === true;
      }
    }

    return isGranted;
  },

  /**
    Returns whether a user has access to update a record of this type. If a
    record is passed that involves personal privileges, it will validate
    whether that particular record is updatable.

    @returns {Boolean}
  */
  canUpdate: function(record) {
    return XT.Model._canDo.call(this, 'update', record);
  },

  /**
    Returns whether a user has access to delete a record of this type. If a
    record is passed that involves personal privileges, it will validate
    whether that particular record is deletable.

    @returns {Boolean}
  */
  canDelete: function(record) {
    return XT.Model._canDo.call(this, 'delete', record);
  },

  /** @private */
  _canDo: function(action, record) {
    var privileges = this.prototype.privileges,
        sessionPrivs = XT.session.privileges,
        isGrantedAll = false,
        isGrantedPersonal = false,
        userName = XT.session.details.username;

    if (sessionPrivs && sessionPrivs.get) {
      // check global privileges
      if (privileges.all && privileges.all[action]) {
        isGrantedAll = sessionPrivs.get(privileges.all[action]) === true;
      }

      // check personal privileges
      if (!isGrantedAll && privileges.personal && privileges.personal[action]) {
        isGrantedPersonal = sessionPrivs.get(privileges.personal[action]) === true;
      }
    }

    // If only personal privileges, check the original attribute cache against
    // the personal attribute list to see if we can update
    if (!isGrantedAll && isGrantedPersonal) {
      var i = 0;
      var username = XT.session.details.username;
      var props = privileges.personal && privileges.personal.properties ?
                  privileges.personal.properties : [];

      isGrantedPersonal = false;
      if (record && record._attrCache) {
        while (!isGrantedPersonal && i < props.length) {
          isGrantedPersonal = m._attrCache[props[i]].get('username') === username;
          i++;
        }
      }
    }

    return isGrantedAll || isGrantedPersonal;
  }

});

<|endoftext|>


<commit_before>define('scribe-plugin-inline-objects',[],function () {

  /**
   * Adds support for inserting inline objects, like embeds, videos and images.
   */
  return function (config) {
    return function (scribe) {
        // define inline objects

        var editorEl = scribe.el.parentNode;

        var templates;
        var activeBlock, activeElement;

        // Load the config.
        $.ajax(config, {success: configLoaded});

        function configLoaded(data) {
          templates = data;
          $(".embed-button", editorEl).click(openFlyout);

          $(".inline-tools button", editorEl.parentNode).click(function(event) {
            var name = $(event.target).data("commandName");
            if (typeof actions[name] === "function") {
              actions[name]();
            }
          });
        }

        function openFlyout(event) {
          var insertFunction ,
              blockPosition,
              flyoutHeight = $(".embed-fly-out").height(),
              buttonHeight = $(".embed-button").height(),
              beforeOrAfter;

          var command = $(event.target).closest("button").data("commandName");
          if (command == "embed-before") {
            blockPosition = activeBlock.position().top - flyoutHeight/2 + buttonHeight /2;
            beforeOrAfter = "before";
          }
          else {
            blockPosition = activeBlock.position().top +
              activeBlock.height()
              + parseInt(activeBlock.css('margin-top'))
              - flyoutHeight/2 + buttonHeight / 2;
            beforeOrAfter = "after";
          }

          $(".embed-fly-out", editorEl)
            .css({
              top: blockPosition,
              left: $(scribe.el).css('padding-left')
            })
            .show();

          //register click handler for toolbar
          var elementToPlaceNear = activeBlock;
          $(".embed-fly-out button").bind("click.inline", function(e) {
            var type = $(e.target).closest("button").data("commandName");
            insertObject(type, elementToPlaceNear, beforeOrAfter);
          })

          $("body").bind("click.inline", function(e) {
            // if a click happens outside of the flyout, close it.
            if ($(e.target).closest(".embed-tools").length === 0) {
              closeFlyout();
            }
          });
        }

        function closeFlyout() {
          $("body").unbind("click.inline");
          $(".embed-fly-out button").unbind("click.inline")
          $(".embed-fly-out").hide();
        }

        function insertObject(objectType, elementToPlaceNear, beforeOrAfter) {
          //derive type from button clicked.
          //emit an event, so handler plugin can pick up.
          scribe.trigger("inline:insert:" + objectType, [
            function(values) {
              scribe.updateContents(function() {
                var html = render(
                    templates[objectType].template,
                    $.extend(templates[objectType].defaults, values)
                );
                $(elementToPlaceNear)[beforeOrAfter](html);
                $(".inline", editorEl).attr("contenteditable", "false");
              });
            }
          ]);
          $(".embed-tools", editorEl).removeClass("active");
        }

        // Insert toolbar.
        scribe.el.addEventListener('mouseover', function (event) {
          var block = $(event.target).closest(".editor>*");
          if (block.length === 1) {
            //var top = blocks[i].offsetTop;
            $(".embed-tools",editorEl)
                .css({
                  width: $(scribe.el).css('padding-left'),
                  top: block.position().top + block.css('margin-top').replace(/[^-\d\.]/g, '') / 2,
                  height: block.height()
                })
                .addClass("active");
            activeBlock = block;
          }
          else {
            $(".embed-tools", editorEl).removeClass("active");
          }
        });

        scribe.el.parentNode.addEventListener('mouseleave', function (event) {
          $(".embed-tools", editorEl).removeClass("active");
        });

        // put the actve class back on if hover back into a button
        $(".embed-tools", editorEl).mouseover(function() {
          $(".embed-tools", editorEl).addClass("active");
        });

        // Overlay options
        $(".editor", editorEl).mouseover( function(e) {
          //check to see if the target is inside of an inline element
          var parents = $(e.target).parents('.inline');
          if (parents.length == 1) {
            //let's position tools over the inline element
            activeElement = parents[parents.length-1];
            showToolbar()
          }
          else {
            hideToolbar();
          }
        });

        function hideToolbar() {
          $(".inline-tools").hide();
          $(editorEl).removeClass("inline-active")
        }

        function showToolbar() {
          var el = $(activeElement);
          var pos = el.position();
          $(editorEl).addClass("inline-active");

          //set size buttons.

          $(".inline-tools .size", editorEl)
            .html($(activeElement).attr("data-size"));

          //set crop
          $(".inline-tools .crop", editorEl)
            .html($(activeElement).attr("data-crop"));

          $(".inline-tools", editorEl)
            .css({
              top: pos.top + parseInt(el.css('margin-top')),
              left: pos.left + parseInt(el.css('margin-left')) + parseInt($(".editor", editorEl).css('margin-left')),
              width: el.width(),
              height: el.height()
            })
            .show();
        }

        function getSizes() {
          return templates[$(activeElement).attr("data-type")].size;
        }

        function getCrops() {
          return templates[$(activeElement).attr("data-type")].crop;
        }

        //TODO: Determine how to handle two adjacent inline elements. Probably skip over?
        var actions = {

          inline_caption: function() {
            var caption = prompt("Caption",
              $(".caption", activeElement).html()
            );
            if (caption) {
              scribe.updateContents(function() {
                $(".caption", activeElement).html(caption);
              });
            }
          },
          //TODO: size/crop isn't working right after you hit the "HUGE" size in images
          inline_size: function() {
            var l = getSizes();
            toggleAttribute("size", l);

            var currentCrop = $(activeElement).attr("data-crop");
            var cropOptions = getCrops();
            //this crop isn't available for the new size option
            if (cropOptions.indexOf(currentCrop) === -1) {
              setValue("crop", cropOptions[0]);
            }
          },
          inline_crop: function() {
            var l = templates[$(activeElement).attr("data-type")].crop;
            toggleAttribute("crop", l);
          },
          inline_up: function() {
             hideToolbar();
            var previousBlock = $(activeElement).prev()[0];
            if (previousBlock) {
              var top = $(activeElement).offset().top;

              scribe.updateContents(function() {
                $(activeElement).after(previousBlock);

                setTimeout(function() {
                  showToolbar();
                  var newTop = $(activeElement).offset().top;
                  window.scrollBy(0, newTop - top)
                }, 0);
              });
            }
          },
          inline_down: function() {
            hideToolbar();
            var nextBlock = $(activeElement).next()[0];
            if (nextBlock) {
              var top = $(activeElement).offset().top;
              scribe.updateContents(function() {
                $(activeElement).before(nextBlock);

                setTimeout(function() {
                  showToolbar();
                  var newTop = $(activeElement).offset().top;
                  window.scrollBy(0, newTop - top)
                }, 0);
              });
            }
          },
          inline_remove: function () {
            scribe.updateContents(function() {
              $(activeElement).remove();
            });
            hideToolbar()
          },
          inline_edit: function () {
            scribe.trigger("inline:edit:" + $(activeElement).attr("data-type"),
              [
                activeElement,
                function(element, values) {
                  var type = $(element).attr("data-type");
                  scribe.updateContents(function() {
                    element.outerHTML =
                      render(
                        templates[type].template,
                        $.extend(templates[type].defaults, values)
                      )
                  });
                }
              ]
            )
          }
        }

        function toggleAttribute(attribute, list) {
          var currentValue = $(activeElement).attr("data-" + attribute);
          var index = list.indexOf(currentValue) + 1;
          if (index >= list.length)
            index = 0;
          setValue(attribute, list[index]);
          if (typeof window.picturefill === "function") {
            setTimeout(function() {
              window.picturefill(activeElement);
            }, 100);
          }
        }

        function setValue(attribute, value) {
          var currentValue = $(activeElement).attr("data-" + attribute);
          scribe.updateContents(function() {
            $(activeElement)
              .removeClass(attribute + "-" + currentValue)
              .addClass(attribute + "-" + value)
              .attr("data-" + attribute, value)
              showToolbar();
          });
        }

        function render(html, dict) {
          for (var k in dict) {
            if (k) {
              html = html.replace(new RegExp("{{" + k + "}}", 'g'), dict[k]);
            }
          }
          return html;
        }
    }
  }
});<commit_msg>json is... json???<commit_after>define('scribe-plugin-inline-objects',[],function () {

  /**
   * Adds support for inserting inline objects, like embeds, videos and images.
   */
  return function (config) {
    return function (scribe) {
        // define inline objects

        var editorEl = scribe.el.parentNode;

        var templates;
        var activeBlock, activeElement;

        // Load the config.
        $.ajax(config, {success: configLoaded, dataType: 'json'});

        function configLoaded(data) {
          templates = data;
          $(".embed-button", editorEl).click(openFlyout);

          $(".inline-tools button", editorEl.parentNode).click(function(event) {
            var name = $(event.target).data("commandName");
            if (typeof actions[name] === "function") {
              actions[name]();
            }
          });
        }

        function openFlyout(event) {
          var insertFunction ,
              blockPosition,
              flyoutHeight = $(".embed-fly-out").height(),
              buttonHeight = $(".embed-button").height(),
              beforeOrAfter;

          var command = $(event.target).closest("button").data("commandName");
          if (command == "embed-before") {
            blockPosition = activeBlock.position().top - flyoutHeight/2 + buttonHeight /2;
            beforeOrAfter = "before";
          }
          else {
            blockPosition = activeBlock.position().top +
              activeBlock.height()
              + parseInt(activeBlock.css('margin-top'))
              - flyoutHeight/2 + buttonHeight / 2;
            beforeOrAfter = "after";
          }

          $(".embed-fly-out", editorEl)
            .css({
              top: blockPosition,
              left: $(scribe.el).css('padding-left')
            })
            .show();

          //register click handler for toolbar
          var elementToPlaceNear = activeBlock;
          $(".embed-fly-out button").bind("click.inline", function(e) {
            var type = $(e.target).closest("button").data("commandName");
            insertObject(type, elementToPlaceNear, beforeOrAfter);
          })

          $("body").bind("click.inline", function(e) {
            // if a click happens outside of the flyout, close it.
            if ($(e.target).closest(".embed-tools").length === 0) {
              closeFlyout();
            }
          });
        }

        function closeFlyout() {
          $("body").unbind("click.inline");
          $(".embed-fly-out button").unbind("click.inline")
          $(".embed-fly-out").hide();
        }

        function insertObject(objectType, elementToPlaceNear, beforeOrAfter) {
          //derive type from button clicked.
          //emit an event, so handler plugin can pick up.
          scribe.trigger("inline:insert:" + objectType, [
            function(values) {
              scribe.updateContents(function() {
                var html = render(
                    templates[objectType].template,
                    $.extend(templates[objectType].defaults, values)
                );
                $(elementToPlaceNear)[beforeOrAfter](html);
                $(".inline", editorEl).attr("contenteditable", "false");
              });
            }
          ]);
          $(".embed-tools", editorEl).removeClass("active");
        }

        // Insert toolbar.
        scribe.el.addEventListener('mouseover', function (event) {
          var block = $(event.target).closest(".editor>*");
          if (block.length === 1) {
            //var top = blocks[i].offsetTop;
            $(".embed-tools",editorEl)
                .css({
                  width: $(scribe.el).css('padding-left'),
                  top: block.position().top + block.css('margin-top').replace(/[^-\d\.]/g, '') / 2,
                  height: block.height()
                })
                .addClass("active");
            activeBlock = block;
          }
          else {
            $(".embed-tools", editorEl).removeClass("active");
          }
        });

        scribe.el.parentNode.addEventListener('mouseleave', function (event) {
          $(".embed-tools", editorEl).removeClass("active");
        });

        // put the actve class back on if hover back into a button
        $(".embed-tools", editorEl).mouseover(function() {
          $(".embed-tools", editorEl).addClass("active");
        });

        // Overlay options
        $(".editor", editorEl).mouseover( function(e) {
          //check to see if the target is inside of an inline element
          var parents = $(e.target).parents('.inline');
          if (parents.length == 1) {
            //let's position tools over the inline element
            activeElement = parents[parents.length-1];
            showToolbar()
          }
          else {
            hideToolbar();
          }
        });

        function hideToolbar() {
          $(".inline-tools").hide();
          $(editorEl).removeClass("inline-active")
        }

        function showToolbar() {
          var el = $(activeElement);
          var pos = el.position();
          $(editorEl).addClass("inline-active");

          //set size buttons.

          $(".inline-tools .size", editorEl)
            .html($(activeElement).attr("data-size"));

          //set crop
          $(".inline-tools .crop", editorEl)
            .html($(activeElement).attr("data-crop"));

          $(".inline-tools", editorEl)
            .css({
              top: pos.top + parseInt(el.css('margin-top')),
              left: pos.left + parseInt(el.css('margin-left')) + parseInt($(".editor", editorEl).css('margin-left')),
              width: el.width(),
              height: el.height()
            })
            .show();
        }

        function getSizes() {
          return templates[$(activeElement).attr("data-type")].size;
        }

        function getCrops() {
          return templates[$(activeElement).attr("data-type")].crop;
        }

        //TODO: Determine how to handle two adjacent inline elements. Probably skip over?
        var actions = {

          inline_caption: function() {
            var caption = prompt("Caption",
              $(".caption", activeElement).html()
            );
            if (caption) {
              scribe.updateContents(function() {
                $(".caption", activeElement).html(caption);
              });
            }
          },
          //TODO: size/crop isn't working right after you hit the "HUGE" size in images
          inline_size: function() {
            var l = getSizes();
            toggleAttribute("size", l);

            var currentCrop = $(activeElement).attr("data-crop");
            var cropOptions = getCrops();
            //this crop isn't available for the new size option
            if (cropOptions.indexOf(currentCrop) === -1) {
              setValue("crop", cropOptions[0]);
            }
          },
          inline_crop: function() {
            var l = templates[$(activeElement).attr("data-type")].crop;
            toggleAttribute("crop", l);
          },
          inline_up: function() {
             hideToolbar();
            var previousBlock = $(activeElement).prev()[0];
            if (previousBlock) {
              var top = $(activeElement).offset().top;

              scribe.updateContents(function() {
                $(activeElement).after(previousBlock);

                setTimeout(function() {
                  showToolbar();
                  var newTop = $(activeElement).offset().top;
                  window.scrollBy(0, newTop - top)
                }, 0);
              });
            }
          },
          inline_down: function() {
            hideToolbar();
            var nextBlock = $(activeElement).next()[0];
            if (nextBlock) {
              var top = $(activeElement).offset().top;
              scribe.updateContents(function() {
                $(activeElement).before(nextBlock);

                setTimeout(function() {
                  showToolbar();
                  var newTop = $(activeElement).offset().top;
                  window.scrollBy(0, newTop - top)
                }, 0);
              });
            }
          },
          inline_remove: function () {
            scribe.updateContents(function() {
              $(activeElement).remove();
            });
            hideToolbar()
          },
          inline_edit: function () {
            scribe.trigger("inline:edit:" + $(activeElement).attr("data-type"),
              [
                activeElement,
                function(element, values) {
                  var type = $(element).attr("data-type");
                  scribe.updateContents(function() {
                    element.outerHTML =
                      render(
                        templates[type].template,
                        $.extend(templates[type].defaults, values)
                      )
                  });
                }
              ]
            )
          }
        }

        function toggleAttribute(attribute, list) {
          var currentValue = $(activeElement).attr("data-" + attribute);
          var index = list.indexOf(currentValue) + 1;
          if (index >= list.length)
            index = 0;
          setValue(attribute, list[index]);
          if (typeof window.picturefill === "function") {
            setTimeout(function() {
              window.picturefill(activeElement);
            }, 100);
          }
        }

        function setValue(attribute, value) {
          var currentValue = $(activeElement).attr("data-" + attribute);
          scribe.updateContents(function() {
            $(activeElement)
              .removeClass(attribute + "-" + currentValue)
              .addClass(attribute + "-" + value)
              .attr("data-" + attribute, value)
              showToolbar();
          });
        }

        function render(html, dict) {
          for (var k in dict) {
            if (k) {
              html = html.replace(new RegExp("{{" + k + "}}", 'g'), dict[k]);
            }
          }
          return html;
        }
    }
  }
});<|endoftext|>


<commit_before>'use strict';

var linkTag = 0, compileTag = 0;

angular.module('tngDirectiveLifecycle', [])
.directive('tngLifecycle', function() {
	console.log('[tng-lifecycle]: construction');

	return {
		controller: function($scope) {
			console.log('[tng-lifecycle]: controller');
		},
		compile: function(tElement) {
			console.log('[tng-lifecycle]: compile');

			tElement.addClass('pretty');
			tElement.append('compile() call order: ' + ++compileTag + '
');

			var preElement;

			return {
				pre: function(scope, iElement) {
					preElement = iElement;

					console.log('[tng-lifecycle]: pre-link');

					iElement.append('pre() (pre-link) call order ' + ++linkTag + '
');
					iElement.append('*** tElement === iElement? => ' + (tElement === iElement) + '
');
				},
				post: function(scope, iElement) {
					console.log('[tng-lifecycle]: post-link');

					iElement.append('post() (post-link) call order ' + ++linkTag + '
');
					iElement.append('*** tElement === iElement? => ' + (tElement === iElement) + '
');
					iElement.append('*** preElement === postElement? => ' + (preElement === iElement));
				}
			};
		}
	};
});<commit_msg>Correction.<commit_after>'use strict';

var linkTag = 0, compileTag = 0;

angular.module('tngDirectiveLifecycle', [])
.directive('tngLifecycle', function() {
	console.log('[tng-lifecycle]: construction');

	return {
		controller: function($scope) {
			console.log('[tng-lifecycle]: controller');
		},
		compile: function(tElement) {
			console.log('[tng-lifecycle]: compile');

			tElement.addClass('pretty');
			tElement.append('compile() call order: ' + ++compileTag + '
');

			var preElement;

			return {
				pre: function(scope, iElement) {
					preElement = iElement;

					console.log('[tng-lifecycle]: pre-link');

					iElement.append('pre() (pre-link) call order: ' + ++linkTag + '
');
					iElement.append('*** tElement === iElement? => ' + (tElement === iElement) + '
');
				},
				post: function(scope, iElement) {
					console.log('[tng-lifecycle]: post-link');

					iElement.append('post() (post-link) call order: ' + ++linkTag + '
');
					iElement.append('*** tElement === iElement? => ' + (tElement === iElement) + '
');
					iElement.append('*** preElement === postElement? => ' + (preElement === iElement));
				}
			};
		}
	};
});<|endoftext|>


<commit_before>(function() {
  const events = new Map();
  const propagationStopped = new WeakMap();
  const immediatePropagationStopped = new WeakMap();

  function before(subject, verb, fn) {
    const source = subject[verb];
    subject[verb] = function() {
      fn.apply(subject, arguments);
      return source.apply(subject, arguments);
    };
    return subject;
  }

  function matches(selectors, target) {
    var queue = [];
    var node = target;

    do {
      if (node.nodeType !== 1) break;
      var matches = selectors.matches(node);
      if (matches.length) {
        queue.push({node: node, observers: matches});
      }
    } while (node = node.parentElement);

    return queue;
  }

  function trackPropagation() {
    propagationStopped.set(this, true);
  }

  function trackImmediate() {
    immediatePropagationStopped.set(this, true);
  }

  function dispatch(selectors, event) {
    before(event, 'stopPropagation', trackPropagation);
    before(event, 'stopImmediatePropagation', trackImmediate);

    var queue = matches(selectors, event.target);
    for (var i = 0, len1 = queue.length; i < len1; i++) {
      if (propagationStopped.has(event)) break;
      var matched = queue[i];

      for (var j = 0, len2 = matched.observers.length; j < len2; j++) {
        if (immediatePropagationStopped.has(event)) break;
        matched.observers[j].data.call(matched.node, event);
      }
    }
  }

  this.on = function(name, selector, fn) {
    var selectors = events.get(name);
    if (!selectors) {
      selectors = new SelectorSet();
      events.set(name, selectors);
      document.addEventListener(name, dispatch.bind(null, selectors), false);
    }
    selectors.add(selector, fn);
  };

  this.off = function(name, selector, fn) {
    const selectors = events.get(name);
    if (selectors) {
      selectors.remove(selector, fn);
    }
  };

  this.fire = function(target, name, detail) {
    return target.dispatchEvent(
      new CustomEvent(name, {
        bubbles: true,
        cancelable: true,
        detail: detail
      })
    );
  };
}).call($);
<commit_msg>var -> const<commit_after>(function() {
  const events = new Map();
  const propagationStopped = new WeakMap();
  const immediatePropagationStopped = new WeakMap();

  function before(subject, verb, fn) {
    const source = subject[verb];
    subject[verb] = function() {
      fn.apply(subject, arguments);
      return source.apply(subject, arguments);
    };
    return subject;
  }

  function matches(selectors, target) {
    var queue = [];
    var node = target;

    do {
      if (node.nodeType !== 1) break;
      var matches = selectors.matches(node);
      if (matches.length) {
        queue.push({node: node, observers: matches});
      }
    } while (node = node.parentElement);

    return queue;
  }

  function trackPropagation() {
    propagationStopped.set(this, true);
  }

  function trackImmediate() {
    immediatePropagationStopped.set(this, true);
  }

  function dispatch(selectors, event) {
    before(event, 'stopPropagation', trackPropagation);
    before(event, 'stopImmediatePropagation', trackImmediate);

    const queue = matches(selectors, event.target);
    for (var i = 0, len1 = queue.length; i < len1; i++) {
      if (propagationStopped.has(event)) break;
      var matched = queue[i];

      for (var j = 0, len2 = matched.observers.length; j < len2; j++) {
        if (immediatePropagationStopped.has(event)) break;
        matched.observers[j].data.call(matched.node, event);
      }
    }
  }

  this.on = function(name, selector, fn) {
    var selectors = events.get(name);
    if (!selectors) {
      selectors = new SelectorSet();
      events.set(name, selectors);
      document.addEventListener(name, dispatch.bind(null, selectors), false);
    }
    selectors.add(selector, fn);
  };

  this.off = function(name, selector, fn) {
    const selectors = events.get(name);
    if (selectors) {
      selectors.remove(selector, fn);
    }
  };

  this.fire = function(target, name, detail) {
    return target.dispatchEvent(
      new CustomEvent(name, {
        bubbles: true,
        cancelable: true,
        detail: detail
      })
    );
  };
}).call($);
<|endoftext|>


<commit_before>
define([], function() {

    var // keypaths to vertices objects in ajax responses
        VERTICES_RESPONSE_KEYPATHS = ['vertices', 'data.vertices'],

        IGNORE_NO_CONVERTER_FOUND_REGEXS = [
            /^user/,
            /^configuration$/,
            /^workspaces?$/,
            /^workspace\?/,
            /^workspace\/create/,
            /^workspace\/all/,
            /^workspace\/diff/,
            /^workspace\/edges/,
            /^workspace\/update/,
            /^workspace\/publish/,
            /^workspace\/undo/,
            /^map\/geocode/,
            /^terms$/,
            /^logout$/,
            /^admin/,
            /^login/,
            /^vertex\/remove-edge/,
            /^vertex\/(un)?resolve/,
            /^vertex\/import/,
            /^vertex\/audit/,
            /^edge/
        ],

        // Custom converters for routes that are more complicated than above,
        // call updateCacheWithVertex and append to updated, return
        // true if handled
        JSON_CONVERTERS = [

            function vertexProperties(json, updated) {
                if (!json.sourceVertexId &&
                    _.isString(json.id) &&
                    _.isObject(json.properties) &&
                    _.keys(json.properties).length) {
                    updated.push(
                        this.updateCacheWithVertex(json, { returnNullIfNotChanged: true })
                    );
                    return true;
                }
            },

            function vertexRelationships(json, updated) {
                var self = this;

                if (json.relationships) {
                    json.relationships.forEach(function(relationship) {
                        if (relationship.vertex) {
                            updated.push(
                                self.updateCacheWithVertex(relationship.vertex, {
                                    returnNullIfNotChanged: true
                                })
                            );
                        }
                    });
                    return true;
                }
            },

            function findPath(json, updated) {
                var self = this;

                if (json.paths) {
                    json.paths.forEach(function(path) {
                        path.forEach(function(vertex) {
                            updated.push(
                                self.updateCacheWithVertex(vertex, { returnNullIfNotChanged: true })
                            );
                        });
                    });
                    return true;
                }
            },

            function vertexProperties(json, updated) {
                if (json.vertex && json.vertex.id && json.properties) {
                    updated.push(this.updateCacheWithVertex(json.vertex, { returnNullIfNotChanged: true }));
                    return true;
                } else if (json.vertex && json.vertex.graphVertexId && json.properties) {
                    updated.push(
                        this.updateCacheWithVertex({
                            id: json.vertex.graphVertexId,
                            properties: json.properties
                        }, { returnNullIfNotChanged: true })
                    );
                    return true;
                }
            },

            function verticesRoot(json, updated) {
                var self = this;
                if (_.isArray(json) && json.length && json[0].id && json[0].properties) {
                    json.forEach(function(vertex) {
                        updated.push(
                            self.updateCacheWithVertex(vertex, { returnNullIfNotChanged: true })
                        );
                    });
                    return true;
                }
            }
        ];

    return withCacheUpdatingAjaxFilters;

    function withCacheUpdatingAjaxFilters() {

        this.after('initialize', function() {
            this.setupAjaxPrefilter();
        });

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

            // Attach converter to ajax requests to merge with cachedVertices,
            // sending updateVertices events
            $.ajaxPrefilter(function(options, originalOptions, xhr) {
                var jsonConverter = options.converters['text json'];
                options.converters['text json'] = function(data) {

                    var json = jsonConverter(data);

                    try {
                        var updated = [],
                            converterFound = _.any(JSON_CONVERTERS, function(c) {
                                if (!json) return;

                                var result = c.call(self, json, updated);
                                return result === true;
                            });

                        if (!converterFound && _.keys(json).length) {
                            var keypathFound = false;

                            VERTICES_RESPONSE_KEYPATHS.forEach(function(paths) {
                                var val = json,
                                    components = paths.indexOf('.') === -1 ? [paths] : paths.split('.');

                                while (val && components.length) {
                                    val = val[components.shift()];
                                }

                                if (val && _.isArray(val) && val.length === 0) {
                                    keypathFound = true;
                                }

                                // Found vertices
                                if (val && self.resemblesVertices(val)) {
                                    keypathFound = true;
                                    val.forEach(function(v) {
                                        updated.push(self.updateCacheWithVertex(v, { returnNullIfNotChanged: true }));
                                    });
                                } else if (!keypathFound) {
                                    keypathFound = true;

                                    // Might be an error if we didn't match and
                                    // getting vertices without updating cache
                                    // and applying patches
                                    if (
                                        !_.some(IGNORE_NO_CONVERTER_FOUND_REGEXS, function(regex) {
                                            return regex.test(options.url);
                                        })
                                    ) {
                                        console.warn('No converter applied for url:', options.url);
                                    }
                                }
                            });
                        }

                        updated = _.compact(updated);
                        if (updated.length) {
                            _.defer(function() {
                                self.trigger('verticesUpdated', {
                                    vertices: updated
                                });
                            });
                        }
                    } catch(e) {
                        console.error('Request failed in prefilter cache phase', e.stack || e.message, options.url);
                    }
                    return json;
                };
            });
        };
    }
});
<commit_msg>Add delete vertex property to ignore list<commit_after>
define([], function() {

    var // keypaths to vertices objects in ajax responses
        VERTICES_RESPONSE_KEYPATHS = ['vertices', 'data.vertices'],

        IGNORE_NO_CONVERTER_FOUND_REGEXS = [
            /^user/,
            /^configuration$/,
            /^workspaces?$/,
            /^workspace\?/,
            /^workspace\/create/,
            /^workspace\/all/,
            /^workspace\/diff/,
            /^workspace\/edges/,
            /^workspace\/update/,
            /^workspace\/publish/,
            /^workspace\/undo/,
            /^map\/geocode/,
            /^terms$/,
            /^logout$/,
            /^admin/,
            /^login/,
            /^vertex\/remove-edge/,
            /^vertex\/(un)?resolve/,
            /^vertex\/import/,
            /^vertex\/audit/,
            /^vertex\/property/,
            /^edge/
        ],

        // Custom converters for routes that are more complicated than above,
        // call updateCacheWithVertex and append to updated, return
        // true if handled
        JSON_CONVERTERS = [

            function vertexProperties(json, updated) {
                if (!json.sourceVertexId &&
                    _.isString(json.id) &&
                    _.isObject(json.properties) &&
                    _.keys(json.properties).length) {
                    updated.push(
                        this.updateCacheWithVertex(json, { returnNullIfNotChanged: true })
                    );
                    return true;
                }
            },

            function vertexRelationships(json, updated) {
                var self = this;

                if (json.relationships) {
                    json.relationships.forEach(function(relationship) {
                        if (relationship.vertex) {
                            updated.push(
                                self.updateCacheWithVertex(relationship.vertex, {
                                    returnNullIfNotChanged: true
                                })
                            );
                        }
                    });
                    return true;
                }
            },

            function findPath(json, updated) {
                var self = this;

                if (json.paths) {
                    json.paths.forEach(function(path) {
                        path.forEach(function(vertex) {
                            updated.push(
                                self.updateCacheWithVertex(vertex, { returnNullIfNotChanged: true })
                            );
                        });
                    });
                    return true;
                }
            },

            function vertexProperties(json, updated) {
                if (json.vertex && json.vertex.id && json.properties) {
                    updated.push(this.updateCacheWithVertex(json.vertex, { returnNullIfNotChanged: true }));
                    return true;
                } else if (json.vertex && json.vertex.graphVertexId && json.properties) {
                    updated.push(
                        this.updateCacheWithVertex({
                            id: json.vertex.graphVertexId,
                            properties: json.properties
                        }, { returnNullIfNotChanged: true })
                    );
                    return true;
                }
            },

            function verticesRoot(json, updated) {
                var self = this;
                if (_.isArray(json) && json.length && json[0].id && json[0].properties) {
                    json.forEach(function(vertex) {
                        updated.push(
                            self.updateCacheWithVertex(vertex, { returnNullIfNotChanged: true })
                        );
                    });
                    return true;
                }
            }
        ];

    return withCacheUpdatingAjaxFilters;

    function withCacheUpdatingAjaxFilters() {

        this.after('initialize', function() {
            this.setupAjaxPrefilter();
        });

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

            // Attach converter to ajax requests to merge with cachedVertices,
            // sending updateVertices events
            $.ajaxPrefilter(function(options, originalOptions, xhr) {
                var jsonConverter = options.converters['text json'];
                options.converters['text json'] = function(data) {

                    var json = jsonConverter(data);

                    try {
                        var updated = [],
                            converterFound = _.any(JSON_CONVERTERS, function(c) {
                                if (!json) return;

                                var result = c.call(self, json, updated);
                                return result === true;
                            });

                        if (!converterFound && _.keys(json).length) {
                            var keypathFound = false;

                            VERTICES_RESPONSE_KEYPATHS.forEach(function(paths) {
                                var val = json,
                                    components = paths.indexOf('.') === -1 ? [paths] : paths.split('.');

                                while (val && components.length) {
                                    val = val[components.shift()];
                                }

                                if (val && _.isArray(val) && val.length === 0) {
                                    keypathFound = true;
                                }

                                // Found vertices
                                if (val && self.resemblesVertices(val)) {
                                    keypathFound = true;
                                    val.forEach(function(v) {
                                        updated.push(self.updateCacheWithVertex(v, { returnNullIfNotChanged: true }));
                                    });
                                } else if (!keypathFound) {
                                    keypathFound = true;

                                    // Might be an error if we didn't match and
                                    // getting vertices without updating cache
                                    // and applying patches
                                    if (
                                        !_.some(IGNORE_NO_CONVERTER_FOUND_REGEXS, function(regex) {
                                            return regex.test(options.url);
                                        })
                                    ) {
                                        console.warn('No converter applied for url:', options.url);
                                    }
                                }
                            });
                        }

                        updated = _.compact(updated);
                        if (updated.length) {
                            _.defer(function() {
                                self.trigger('verticesUpdated', {
                                    vertices: updated
                                });
                            });
                        }
                    } catch(e) {
                        console.error('Request failed in prefilter cache phase', e.stack || e.message, options.url);
                    }
                    return json;
                };
            });
        };
    }
});
<|endoftext|>


<commit_before>// Given a set of distinct integers, nums, return all possible subsets.
//
// Note: The solution set must not contain duplicate subsets.
//
// For example,
// If nums = [1,2,3], a solution is:
//
// [
//  [3],
//  [1],
//  [2],
//  [1,2,3],
//  [1,3],
//  [2,3],
//  [1,2],
//  []
// ]

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
let subsets = function(nums) {
  let results = [];
  let n = nums.length;
  let total = 1;

  for (let i = 1; i < n; i++) {
    total = (total << 1) + 1;
  }

  for (let i = 0; i <= total; i++) {
    let current = [];

    for (let j = 0; j < n; j++) {
      if (((i >> j) & 1) === 1) {
        current.push(nums[j]);
      }
    }

    results.push(current);
  }

  return results;
};
<commit_msg>backtracing solution for No.78<commit_after>// Given a set of distinct integers, nums, return all possible subsets.
//
// Note: The solution set must not contain duplicate subsets.
//
// For example,
// If nums = [1,2,3], a solution is:
//
// [
//  [3],
//  [1],
//  [2],
//  [1,2,3],
//  [1,3],
//  [2,3],
//  [1,2],
//  []
// ]

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
let subsets = function(nums) {
  let results = [[]];

  if (nums.length === 0) {
    return results;
  }

  let stack = [0];
  let n = nums.length - 1;

  while (stack.length > 0) {
    let current = [];
    for (let i = 0, n = stack.length; i < n; i++) {
      current.push(nums[stack[i]]);
    }
    results.push(current);

    let last = stack[stack.length - 1];
    if (last < n) {
      stack.push(last + 1);
    } else {
      stack.pop();

      if (stack[stack.length - 1] < n) {
        stack[stack.length - 1] = stack[stack.length - 1] + 1;
      }
    }
  }

  return results;
};
<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 *
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
var copyDirectory = require( './copyDirectory' );
var ChipperStringUtils = require( '../../common/ChipperStringUtils' );
var ChipperConstants = require( '../../common/ChipperConstants' );
// var generatePhETIOAPIDocs = require( './generatePhETIOAPIDocs' );

// constants
var DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';
var LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js',
  '../phet-io-wrappers/common/js/SimIFrameClient.js',
  '../phet-io-wrappers/common/js/WrapperTypes.js',
  '../phet-io-wrappers/common/js/assert.js',
  '../phet-io-wrappers/common/js/WrapperUtils.js' ];

var LIB_DIR = ChipperConstants.BUILD_DIR + '/lib';
var LIB_OUTPUT_FILE = 'phetio.js';
var LIB_COPYRIGHT_HEADER = '// Copyright 2002-2017, University of Colorado Boulder\n' +
                           '// This PhET-iO file requires a license\n' +
                           '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                           '// For licensing, please contact phethelp@colorado.edu';

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended.
 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleLib = function( grunt, filter ) {

  // TODO: chipper#101 eek, this is scary! we are importing from the node_modules dir. ideally we should just have uglify-js installed once in sherpa?
  var uglify = require( '../../../node_modules/uglify-js/tools/node' );// eslint-disable-line require-statement-match

  // output the SimIFrameClient.js and WrapperUtils.js to the top level lib (not password-protected), see https://github.com/phetsims/phet-io/issues/353
  grunt.file.mkdir( LIB_DIR );

  var consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    var contents = grunt.file.read( libFile );

    var filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  var minified = uglify.minify( consolidated, {
    fromString: true,
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  grunt.file.write( LIB_DIR + '/' + LIB_OUTPUT_FILE, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy the appropriate resources and files to the build folder needed for the develeopment guide.
 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleDevGuide = function( grunt, filter ) {
  var devguideHTML = grunt.file.read( '../phet-io-website/root/devguide/index.html' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/css/bootstrap.min.css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/js/bootstrap.min.js', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/font-awesome-4.5.0/css/font-awesome.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/jquery-1.12.3.min.js', 'https://code.jquery.com/jquery-1.12.3.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/css/', './css/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/', './js/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/styles/tomorrow-night-bright.css', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/tomorrow-night-bright.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/highlight.js', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/favicon.ico', './favicon.ico' );

  // Remove the navbar and footer div tags
  var firstNavbarLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="navbar"' );
  devguideHTML = firstNavbarLine ? ChipperStringUtils.replaceAll( devguideHTML, firstNavbarLine, '' ) : devguideHTML;

  var firstFooterLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="footer"' );
  devguideHTML = firstFooterLine ? ChipperStringUtils.replaceAll( devguideHTML, firstFooterLine, '' ) : devguideHTML;

  grunt.file.write( ChipperConstants.BUILD_DIR + '/docs/devguide.html', devguideHTML );
  copyDirectory( grunt, '../phet-io-website/root/assets/css', ChipperConstants.BUILD_DIR + '/docs/css', filter );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io.js' );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io-ga.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io-ga.js' );
  grunt.file.copy( '../phet-io-website/root/assets/favicon.ico', './' + ChipperConstants.BUILD_DIR + '/docs/favicon.ico' );

};

module.exports = function( grunt, buildConfig ) {

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  var filterWrapper = function( abspath, contents ) {
    var originalContents = contents + '';

    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', buildConfig.name );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', buildConfig.version );
      contents = ChipperStringUtils.replaceAll( contents, 'isBuilt: false', 'isBuilt: true' );
    }
    if ( abspath.indexOf( '.html' ) >= 0 ) {

      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/lodash-4.17.4.min.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/font-awesome-4.5.0/css/font-awesome.min.css"',
        '"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/jquery-2.1.0.min.js"',
        '"https://code.jquery.com/jquery-2.2.3.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/jquery-ui-1.8.24.min.js"',
        '"https://code.jquery.com/ui/1.8.24/jquery-ui.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/d3-4.2.2.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"'
      );

      //TODO: these probably don't need to be repeated for a different path
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/lodash-4.17.4.min.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/font-awesome-4.5.0/css/font-awesome.min.css"',
        '"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/jquery-2.1.0.min.js"',
        '"https://code.jquery.com/jquery-2.2.3.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/jquery-ui-1.8.24.min.js"',
        '"https://code.jquery.com/ui/1.8.24/jquery-ui.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/d3-4.2.2.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"'
      );

      /*
       * Remove individual common code imports because they are all in phetio.js
       */
      // This returns the whole line that contains this substring, so it can be removed
      var firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );
      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' );
      }
      var firstWrapperUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperUtils.js">' );
      if ( firstWrapperUtilsLine && firstWrapperUtilsLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperUtilsLine, '' );
      }
      var firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' );
      }
      var firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'SimIFrameClient.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' );
      }
      var firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' );
      }

      // For info about phetio.js, see the end of this file
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phetio.js}}-->',
        '<script type="text/javascript" src="../../lib/phetio.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script type="text/javascript" src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico">'
      );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, 'phet-io-wrappers/common/', 'common/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

  // Load the master list of all wrappers
  var wrappers = grunt.file.read( '../chipper/data/wrappers' ).toString().split( '\r\n' );

  // Files and directories from wrapper folders that we don't want to copy
  var wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( 'phet-io-wrappers/common' );
  wrappers.forEach( function( wrapper ) {

    var wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    var wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );
    copyDirectory( grunt, '../' + wrapper, ChipperConstants.BUILD_DIR + '/wrappers/' + wrapperName, filterWrapper,
      { blacklist: wrappersBlacklist } );
  } );

  handleDevGuide( grunt );

  handleLib( grunt, filterWrapper );

  // Generate API Documentation
  // TODO: these are broken right now, the cause has something to do with the string plugin loading chipper's localeInfo.js
  // TODO: see https://github.com/phetsims/phet-io/issues/972 for more info
  // generatePhETIOAPIDocs( grunt, buildConfig );
};<commit_msg>split using regex, https://github.com/phetsims/chipper/issues/590<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 *
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
var copyDirectory = require( './copyDirectory' );
var ChipperStringUtils = require( '../../common/ChipperStringUtils' );
var ChipperConstants = require( '../../common/ChipperConstants' );
// var generatePhETIOAPIDocs = require( './generatePhETIOAPIDocs' );

// constants
var DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';
var LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js',
  '../phet-io-wrappers/common/js/SimIFrameClient.js',
  '../phet-io-wrappers/common/js/WrapperTypes.js',
  '../phet-io-wrappers/common/js/assert.js',
  '../phet-io-wrappers/common/js/WrapperUtils.js' ];

var LIB_DIR = ChipperConstants.BUILD_DIR + '/lib';
var LIB_OUTPUT_FILE = 'phetio.js';
var LIB_COPYRIGHT_HEADER = '// Copyright 2002-2017, University of Colorado Boulder\n' +
                           '// This PhET-iO file requires a license\n' +
                           '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                           '// For licensing, please contact phethelp@colorado.edu';

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended.
 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleLib = function( grunt, filter ) {

  // TODO: chipper#101 eek, this is scary! we are importing from the node_modules dir. ideally we should just have uglify-js installed once in sherpa?
  var uglify = require( '../../../node_modules/uglify-js/tools/node' );// eslint-disable-line require-statement-match

  // output the SimIFrameClient.js and WrapperUtils.js to the top level lib (not password-protected), see https://github.com/phetsims/phet-io/issues/353
  grunt.file.mkdir( LIB_DIR );

  var consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    var contents = grunt.file.read( libFile );

    var filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  var minified = uglify.minify( consolidated, {
    fromString: true,
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  grunt.file.write( LIB_DIR + '/' + LIB_OUTPUT_FILE, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy the appropriate resources and files to the build folder needed for the develeopment guide.
 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleDevGuide = function( grunt, filter ) {
  var devguideHTML = grunt.file.read( '../phet-io-website/root/devguide/index.html' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/css/bootstrap.min.css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/js/bootstrap.min.js', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/font-awesome-4.5.0/css/font-awesome.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/jquery-1.12.3.min.js', 'https://code.jquery.com/jquery-1.12.3.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/css/', './css/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/', './js/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/styles/tomorrow-night-bright.css', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/tomorrow-night-bright.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/highlight.js', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/favicon.ico', './favicon.ico' );

  // Remove the navbar and footer div tags
  var firstNavbarLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="navbar"' );
  devguideHTML = firstNavbarLine ? ChipperStringUtils.replaceAll( devguideHTML, firstNavbarLine, '' ) : devguideHTML;

  var firstFooterLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="footer"' );
  devguideHTML = firstFooterLine ? ChipperStringUtils.replaceAll( devguideHTML, firstFooterLine, '' ) : devguideHTML;

  grunt.file.write( ChipperConstants.BUILD_DIR + '/docs/devguide.html', devguideHTML );
  copyDirectory( grunt, '../phet-io-website/root/assets/css', ChipperConstants.BUILD_DIR + '/docs/css', filter );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io.js' );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io-ga.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io-ga.js' );
  grunt.file.copy( '../phet-io-website/root/assets/favicon.ico', './' + ChipperConstants.BUILD_DIR + '/docs/favicon.ico' );

};

module.exports = function( grunt, buildConfig ) {

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  var filterWrapper = function( abspath, contents ) {
    var originalContents = contents + '';

    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', buildConfig.name );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', buildConfig.version );
      contents = ChipperStringUtils.replaceAll( contents, 'isBuilt: false', 'isBuilt: true' );
    }
    if ( abspath.indexOf( '.html' ) >= 0 ) {

      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/lodash-4.17.4.min.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/font-awesome-4.5.0/css/font-awesome.min.css"',
        '"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/jquery-2.1.0.min.js"',
        '"https://code.jquery.com/jquery-2.2.3.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/jquery-ui-1.8.24.min.js"',
        '"https://code.jquery.com/ui/1.8.24/jquery-ui.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../sherpa/lib/d3-4.2.2.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"'
      );

      //TODO: these probably don't need to be repeated for a different path
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/lodash-4.17.4.min.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/font-awesome-4.5.0/css/font-awesome.min.css"',
        '"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/jquery-2.1.0.min.js"',
        '"https://code.jquery.com/jquery-2.2.3.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/jquery-ui-1.8.24.min.js"',
        '"https://code.jquery.com/ui/1.8.24/jquery-ui.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../sherpa/lib/d3-4.2.2.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"'
      );

      /*
       * Remove individual common code imports because they are all in phetio.js
       */
      // This returns the whole line that contains this substring, so it can be removed
      var firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );
      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' );
      }
      var firstWrapperUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperUtils.js">' );
      if ( firstWrapperUtilsLine && firstWrapperUtilsLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperUtilsLine, '' );
      }
      var firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' );
      }
      var firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'SimIFrameClient.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' );
      }
      var firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' );
      }

      // For info about phetio.js, see the end of this file
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phetio.js}}-->',
        '<script type="text/javascript" src="../../lib/phetio.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script type="text/javascript" src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico">'
      );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, 'phet-io-wrappers/common/', 'common/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

  // Load the master list of all wrappers
  var wrappers = grunt.file.read( '../chipper/data/wrappers' ).toString().split( /\r?\n/ );

  // Files and directories from wrapper folders that we don't want to copy
  var wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( 'phet-io-wrappers/common' );
  wrappers.forEach( function( wrapper ) {

    var wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    var wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );
    copyDirectory( grunt, '../' + wrapper, ChipperConstants.BUILD_DIR + '/wrappers/' + wrapperName, filterWrapper,
      { blacklist: wrappersBlacklist } );
  } );

  handleDevGuide( grunt );

  handleLib( grunt, filterWrapper );

  // Generate API Documentation
  // TODO: these are broken right now, the cause has something to do with the string plugin loading chipper's localeInfo.js
  // TODO: see https://github.com/phetsims/phet-io/issues/972 for more info
  // generatePhETIOAPIDocs( grunt, buildConfig );
};<|endoftext|>