<commit_before>"use strict";

Object.assign( gsuiPianoroll.prototype, {
	managercallDuplicating( blcsMap, valA ) {
		const obj = {},
			mapIds = new Map();

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				nId = ++this._idMax,
				copy = Object.assign( {}, d );

			copy.when += valA;
			copy.prev =
			copy.next = null;
			obj[ id ] = { selected: false };
			obj[ nId ] =
			this.data[ nId ] = copy;
			mapIds.set( id, nId );
			d.selected = false;
		} );
		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ];

			if ( blcsMap.has( d.next ) ) {
				const idCurr = mapIds.get( id ),
					idNext = mapIds.get( d.next );

				obj[ idCurr ].next = idNext;
				obj[ idNext ].prev = idCurr;
			}
		} );
		this.onchange( obj );
	},
	managercallSelecting( blcsMap ) {
		const obj = {};

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				selected = !d.selected;

			obj[ id ] = { selected };
			d.selected = selected;
		} );
		this.onchange( obj );
	},
	managercallMoving( blcsMap, valA, valB ) {
		const obj = {},
			when = Math.abs( valA ) > .000001 ? valA : 0;

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				o = {};

			obj[ id ] = o;
			if ( when ) {
				o.when =
				d.when += when;
			}
			if ( valB ) {
				o.key =
				d.key -= valB;
			}
		} );
		this.onchange( obj );
	},
	managercallDeleting( blcsMap ) {
		const obj = {};

		blcsMap.forEach( ( _blc, id ) => {
			const { prev, next } = this.data[ id ];

			obj[ id ] = undefined;
			delete this.data[ id ];
			if ( prev !== null ) {
				const objPrev = obj[ prev ];

				if ( !( prev in obj ) || objPrev !== undefined ) {
					objPrev
						? objPrev.next = null
						: obj[ prev ] = { next: null };
				}
			}
			if ( next !== null ) {
				const objNext = obj[ next ];

				if ( !( next in obj ) || objNext !== undefined ) {
					objNext
						? objNext.prev = null
						: obj[ next ] = { prev: null };
				}
			}
		} );
		this.__unselectBlocks( obj );
		this.onchange( obj );
	},
	managercallCroppingB( blcsMap, valA ) {
		const obj = {};

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				attRel = d.attack + d.release,
				duration = d.duration + valA,
				keyobj = { duration };

			if ( duration < attRel ) {
				d.attack =
				keyobj.attack = +( d.attack / attRel * duration ).toFixed( 3 );
				d.release =
				keyobj.release = +( d.release / attRel * duration ).toFixed( 3 );
			}
			obj[ id ] = keyobj;
			d.duration = duration;
		} );
		this.onchange( obj );
	},

	// .........................................................................
	managercallAttack( blcsMap, valA ) {
		this._managercallAttRel( "attack", blcsMap, valA );
	},
	managercallRelease( blcsMap, valA ) {
		this._managercallAttRel( "release", blcsMap, valA );
	},
	_managercallAttRel( prop, blcsMap, incr ) {
		const obj = {};

		blcsMap.forEach( this._managercallAttRelEach.bind( this, obj, prop, incr ) );
		this.onchange( obj );
	},
	_managercallAttRelEach( obj, prop, incr, _blc, id ) {
		const d = this.data[ id ],
			val = +( d[ prop ] + incr ).toFixed( 3 );

		obj[ id ] = { [ prop ]: val };
		d[ prop ] = val;
	},
} );
<commit_msg>gsuiPianoroll: duplicate joined keys, fix<commit_after>"use strict";

Object.assign( gsuiPianoroll.prototype, {
	managercallDuplicating( blcsMap, valA ) {
		const obj = {},
			mapIds = new Map();

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				nId = `${ ++this._idMax }`,
				copy = Object.assign( {}, d );

			copy.when += valA;
			copy.prev =
			copy.next = null;
			obj[ id ] = { selected: false };
			obj[ nId ] =
			this.data[ nId ] = copy;
			mapIds.set( id, nId );
			d.selected = false;
		} );
		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ];

			if ( blcsMap.has( d.next ) ) {
				const idCurr = mapIds.get( id ),
					idNext = mapIds.get( d.next );

				this.data[ idCurr ].next = obj[ idCurr ].next = idNext;
				this.data[ idNext ].prev = obj[ idNext ].prev = idCurr;
			}
		} );
		this.onchange( obj );
	},
	managercallSelecting( blcsMap ) {
		const obj = {};

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				selected = !d.selected;

			obj[ id ] = { selected };
			d.selected = selected;
		} );
		this.onchange( obj );
	},
	managercallMoving( blcsMap, valA, valB ) {
		const obj = {},
			when = Math.abs( valA ) > .000001 ? valA : 0;

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				o = {};

			obj[ id ] = o;
			if ( when ) {
				o.when =
				d.when += when;
			}
			if ( valB ) {
				o.key =
				d.key -= valB;
			}
		} );
		this.onchange( obj );
	},
	managercallDeleting( blcsMap ) {
		const obj = {};

		blcsMap.forEach( ( _blc, id ) => {
			const { prev, next } = this.data[ id ];

			obj[ id ] = undefined;
			delete this.data[ id ];
			if ( prev !== null ) {
				const objPrev = obj[ prev ];

				if ( !( prev in obj ) || objPrev !== undefined ) {
					objPrev
						? objPrev.next = null
						: obj[ prev ] = { next: null };
				}
			}
			if ( next !== null ) {
				const objNext = obj[ next ];

				if ( !( next in obj ) || objNext !== undefined ) {
					objNext
						? objNext.prev = null
						: obj[ next ] = { prev: null };
				}
			}
		} );
		this.__unselectBlocks( obj );
		this.onchange( obj );
	},
	managercallCroppingB( blcsMap, valA ) {
		const obj = {};

		blcsMap.forEach( ( _blc, id ) => {
			const d = this.data[ id ],
				attRel = d.attack + d.release,
				duration = d.duration + valA,
				keyobj = { duration };

			if ( duration < attRel ) {
				d.attack =
				keyobj.attack = +( d.attack / attRel * duration ).toFixed( 3 );
				d.release =
				keyobj.release = +( d.release / attRel * duration ).toFixed( 3 );
			}
			obj[ id ] = keyobj;
			d.duration = duration;
		} );
		this.onchange( obj );
	},

	// .........................................................................
	managercallAttack( blcsMap, valA ) {
		this._managercallAttRel( "attack", blcsMap, valA );
	},
	managercallRelease( blcsMap, valA ) {
		this._managercallAttRel( "release", blcsMap, valA );
	},
	_managercallAttRel( prop, blcsMap, incr ) {
		const obj = {};

		blcsMap.forEach( this._managercallAttRelEach.bind( this, obj, prop, incr ) );
		this.onchange( obj );
	},
	_managercallAttRelEach( obj, prop, incr, _blc, id ) {
		const d = this.data[ id ],
			val = +( d[ prop ] + incr ).toFixed( 3 );

		obj[ id ] = { [ prop ]: val };
		d[ prop ] = val;
	},
} );
<|endoftext|>


<commit_before>// handles the images dialog on content.php
var imagesDialog = {

    moduleId: null,

    init:function(){

        // setup dropzone
        $("#drop").dropzone({
            url: "/api/file/post",
            clickable: true,
            success: function(file, response){
                var image = jQuery.parseJSON(response);

                var filename = image.filename;

                var match = ko.utils.arrayFirst(contentModel.images(), function (item) {
                                return item.filename === filename;
                            });

                if (!match) {
                    contentModel.images.push(image);
                    contentModel.newimages.push(image);
                }
            }

        });

    },

    addImage:function(src, t_src, filename){

      var images_count = $('div.img').length + 1;

      var uniqId = 'image'+images_count;

      if(imagesDialog.type=='slideshow'){  // add image (thumb) to slideshow

        var html = '<input type="text" value="" placeholder="Enter caption" maxwidth="140">';

        html += '<button type="button" class="secondary-button add-image"></button>';

        $('#desc').find('div#'+imagesDialog.moduleId+' .add-image').remove();

        $('#desc').find('div#'+imagesDialog.moduleId+
        ' div').append(
          html
        );

        $('#desc').respondHandleEvents();

      }
      else{ // add image
          var divId = 'imagecontainer'+images_count;

          var href = '';

          var html = '
';
          html += imagesDialog.getImageHtml('none', uniqId, src, href, ' ');
          html += '
';

          $('#desc').respondAppend(html);
      }

      $('#imagesDialog').modal('hide');

    },

    // gets the image HTML
    getImageHtml:function(position, imageId, src, href, content){

    	if(content == ' '){
	    	content = 'Add your content here or click the settings icon to change the image layout...';
    	}

		if(position=='left'){
			if(href==''){
				html = '

'+content+'
';
			}
			else{
				html = '

'+content+'
';
			}
		}
		else if(position=='right'){
			if(href==''){
				html = '
'+content+'

';
			}
			else{
				html = '
'+content+'

';
			}
		}
		else{ // for no text
			if(href==''){
				html = '

';
			}
			else{
				html = '

';
			}
		}

		return html;
	},

    // shows the images dialog
    show:function(type, moduleId){

        imagesDialog.type = type;
        imagesDialog.moduleId = moduleId;

        contentModel.updateImages();

        $('#imagesDialog').modal('show');
    }
}

$(document).ready(function(){
  imagesDialog.init();
});<commit_msg>Fix image upload for sub-directory installs<commit_after>// handles the images dialog on content.php
var imagesDialog = {

    moduleId: null,

    init:function(){

        // setup dropzone
        $("#drop").dropzone({
            url: "api/file/post",
            clickable: true,
            success: function(file, response){
                var image = jQuery.parseJSON(response);

                var filename = image.filename;

                var match = ko.utils.arrayFirst(contentModel.images(), function (item) {
                                return item.filename === filename;
                            });

                if (!match) {
                    contentModel.images.push(image);
                    contentModel.newimages.push(image);
                }
            }

        });

    },

    addImage:function(src, t_src, filename){

      var images_count = $('div.img').length + 1;

      var uniqId = 'image'+images_count;

      if(imagesDialog.type=='slideshow'){  // add image (thumb) to slideshow

        var html = '<input type="text" value="" placeholder="Enter caption" maxwidth="140">';

        html += '<button type="button" class="secondary-button add-image"></button>';

        $('#desc').find('div#'+imagesDialog.moduleId+' .add-image').remove();

        $('#desc').find('div#'+imagesDialog.moduleId+
        ' div').append(
          html
        );

        $('#desc').respondHandleEvents();

      }
      else{ // add image
          var divId = 'imagecontainer'+images_count;

          var href = '';

          var html = '
';
          html += imagesDialog.getImageHtml('none', uniqId, src, href, ' ');
          html += '
';

          $('#desc').respondAppend(html);
      }

      $('#imagesDialog').modal('hide');

    },

    // gets the image HTML
    getImageHtml:function(position, imageId, src, href, content){

    	if(content == ' '){
	    	content = 'Add your content here or click the settings icon to change the image layout...';
    	}

		if(position=='left'){
			if(href==''){
				html = '

'+content+'
';
			}
			else{
				html = '

'+content+'
';
			}
		}
		else if(position=='right'){
			if(href==''){
				html = '
'+content+'

';
			}
			else{
				html = '
'+content+'

';
			}
		}
		else{ // for no text
			if(href==''){
				html = '

';
			}
			else{
				html = '

';
			}
		}

		return html;
	},

    // shows the images dialog
    show:function(type, moduleId){

        imagesDialog.type = type;
        imagesDialog.moduleId = moduleId;

        contentModel.updateImages();

        $('#imagesDialog').modal('show');
    }
}

$(document).ready(function(){
  imagesDialog.init();
});<|endoftext|>


<commit_before>game.GameTimerManager = Object.extend ({
	init: function(x, y, settings){
		this.now = new Date().getTime();
		this.lastCreep = new Date(). getTime();
    this.paused = false;
		this.alwaysUpdate = true;

	},

    update: function(){
     	this.now = new Date().getTime()
      this.goldTimerCheck(this);
      this.creepTimerCheck();

     	return true;
    },
      goldTimerCheck: function(){
        if(Math.round(this.now/1000)%20 ===0 && (this.now - this.lastCreep >= 1000)){
           game.data.gold += 1;
           console.log("Current gold: " + game.data.gold) ;
      }

      },

     creepTimerCheck: function(){
      if(Math.round(this.now/1000)%10 ===0 && (this.now - this.lastCreep >= 1000)){
        this.lastCreep = this.now;
        var creepe = me.pool.pull("EnemyCreep", 1000, 0, {});
        me.game.world.addChild(creepe, 5);

      }

     }
});

    game.HeroDeathManager = Object.extend({
      init: function(x, y, settings){
      this.alwaysUpdate = true;
      this.gameOver = false;

    },

  update: function(){
    if(game.data.player.dead) {
       me.game.world.removeChild(game.data.player);
       me.state.current().resetPlayer(10, 0);

      }

      return true;
  }

});

    game.ExperienceManager = Object.extend({
      init: function(x, y, settings){
        this.alwaysUpdate = true;

      },

        update: function () {
          if(game.data.win === true && !this.gameOver){
              game.data.exp += 10;
              this.gameOver = true;
          }else if(game.data.win === false && !this.gameOver) {
              game.data.exp += 1;
              this.gameOver = true;

          }

          console.log(gmae.data.exp);

          return true;

      }

    });

// The game manager removes the player if he's  dead and sets him back

// we have refactored the game manager as much as possible

// There are some other things that we can also refactor
<commit_msg>Video 37 commented<commit_after>game.GameTimerManager = Object.extend ({
	init: function(x, y, settings){
		this.now = new Date().getTime();
		this.lastCreep = new Date(). getTime();
    this.paused = false;
		this.alwaysUpdate = true;

	},

    update: function(){
     	this.now = new Date().getTime()
      this.goldTimerCheck(this);
      this.creepTimerCheck();

     	return true;
    },
      goldTimerCheck: function(){
        if(Math.round(this.now/1000)%20 ===0 && (this.now - this.lastCreep >= 1000)){
           game.data.gold += 1;
           console.log("Current gold: " + game.data.gold) ;
      }

      },

     creepTimerCheck: function(){
      if(Math.round(this.now/1000)%10 ===0 && (this.now - this.lastCreep >= 1000)){
        this.lastCreep = this.now;
        var creepe = me.pool.pull("EnemyCreep", 1000, 0, {});
        me.game.world.addChild(creepe, 5);

      }

     }
});

    game.HeroDeathManager = Object.extend({
      init: function(x, y, settings){
      this.alwaysUpdate = true;
      this.gameOver = false;

    },

  update: function(){
    if(game.data.player.dead) {
       me.game.world.removeChild(game.data.player);
       me.state.current().resetPlayer(10, 0);

      }

      return true;
  }

});

    game.ExperienceManager = Object.extend({
      init: function(x, y, settings){
        this.alwaysUpdate = true;

      },

        update: function () {
          if(game.data.win === true && !this.gameOver){
              game.data.exp += 10;
              this.gameOver = true;
          }else if(game.data.win === false && !this.gameOver) {
              game.data.exp += 1;
              this.gameOver = true;

          }

          console.log(game.data.exp);

          return true;

      }

    });

// The game manager removes the player if he's  dead and sets him back

// we have refactored the game manager as much as possible

// There are some other things that we can also refactor

// this.gameOver is a quick fix for all the madness that is going on

//this.game over is the global variable we are using to call the game over screen

//We only get into each of those if statements if game over is false

// if it is true we can not add any more points

// we need to apply this.gameOver to something that will call the game over clean

<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.ui',
  name: 'PropertyView',
  extendsModel: 'foam.ui.AsyncViewLoader',

  documentation: function() {/*
    Apply this trait to a $$DOC{ref:'BaseView'} (such as $$DOC{ref:'HTMLView'}).

Used by $$DOC{ref:'foam.ui.DetailView'} to generate a sub-$$DOC{ref:'foam.ui.View'} for one
    $$DOC{ref:'Property'}. The $$DOC{ref:'foam.ui.View'} chosen can be based off the
    $$DOC{ref:'Property.view',text:'Property.view'} value, the $$DOC{ref:'.innerView'} value, or
    $$DOC{ref:'.args'}.model_.
  */},

  properties: [
    {
      name: 'prop',
      type: 'Property',
      documentation: function() {/*
          The $$DOC{ref:'Property'} for which to generate a $$DOC{ref:'foam.ui.View'}.
      */},
      postSet: function(old, nu) {
        if ( old && this.bound_ ) this.unbindData(this.data);
        if ( nu && ! this.bound_ ) this.bindData(this.data);
        this.args = nu;
        this.model = nu.view;
      }
    },
    {
      name: 'data',
      documentation: function() {/*
          The $$DOC{ref:'.data'} for which to generate a $$DOC{ref:'foam.ui.View'}.
      */},
      postSet: function(old, nu) {
        if ( old && this.bound_ ) this.unbindData(old);
        if ( nu ) this.bindData(nu);
      }
    },
    {
      name: 'childData'
    },
    {
      name: 'innerView',
      help: 'Override for prop.view',
      documentation: function() {/*
        The optional name of the desired sub-$$DOC{ref:'foam.ui.View'}. If not specified,
        prop.$$DOC{ref:'Property.view'} is used. DEPRECATED. Use $$DOC{ref:'.model'} instead.
      */},
      postSet: function(old,nu) {
        this.model = nu;
      }
    },
    {
      name: 'view',
      type: 'foam.ui.View',
      adapt: function(_, v) { return v && v.toView_ ? v.toView_() : v; },
      documentation: function() {/*
        The new sub-$$DOC{ref:'foam.ui.View'} generated for the given $$DOC{ref:'Property'}.
      */}
    },
    {
      name: 'bound_',
      model_: 'BooleanProperty',
      defaultValue: false
    }
  ],

  methods: {
    unbindData: function(oldData) {
      if ( ! this.bound_ || ! oldData || ! this.prop ) return;
      var pValue = oldData.propertyValue(this.prop.name);
      Events.unlink(pValue, this.childData$);
      this.bound_ = false;
    },

    bindData: function(data) {
      if ( this.bound_ || ! data || ! this.prop) return;
      var pValue = data.propertyValue(this.prop.name);
      Events.link(pValue, this.childData$);
      this.bound_ = true;
    },

    toString: function() { /* Name info. */ return 'PropertyView(' + this.prop.name + ', ' + this.view + ')'; },

    destroy: function( isParentDestroyed ) { /* Passthrough to $$DOC{ref:'.view'} */
      // always unbind, since if our parent was the top level destroy we need
      // to unhook if we were created as an addSelfDataChild
      this.unbindData(this.data);
      this.SUPER( isParentDestroyed );
    },

    construct: function() {
      // if not bound yet and we do have data set, bind it
      this.bindData(this.data);
      this.SUPER();
    },

    finishRender: function() {
      this.SUPER();
      this.view.prop = this.prop;
    },

    addDataChild: function(child) {
      Events.link(this.childData$, child.data$);
      this.addChild(child);
    }
  }

});
<commit_msg>PropertyView: Fixed potential order-of-init problem for prop.view vs. innerView<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.ui',
  name: 'PropertyView',
  extendsModel: 'foam.ui.AsyncViewLoader',

  documentation: function() {/*
    Apply this trait to a $$DOC{ref:'BaseView'} (such as $$DOC{ref:'HTMLView'}).

Used by $$DOC{ref:'foam.ui.DetailView'} to generate a sub-$$DOC{ref:'foam.ui.View'} for one
    $$DOC{ref:'Property'}. The $$DOC{ref:'foam.ui.View'} chosen can be based off the
    $$DOC{ref:'Property.view',text:'Property.view'} value, the $$DOC{ref:'.innerView'} value, or
    $$DOC{ref:'.args'}.model_.
  */},

  properties: [
    {
      name: 'prop',
      type: 'Property',
      documentation: function() {/*
          The $$DOC{ref:'Property'} for which to generate a $$DOC{ref:'foam.ui.View'}.
      */},
      postSet: function(old, nu) {
        if ( old && this.bound_ ) this.unbindData(this.data);
        if ( nu && ! this.bound_ ) this.bindData(this.data);
        this.args = nu;
        this.model = this.innerView || nu.view;
      }
    },
    {
      name: 'data',
      documentation: function() {/*
          The $$DOC{ref:'.data'} for which to generate a $$DOC{ref:'foam.ui.View'}.
      */},
      postSet: function(old, nu) {
        if ( old && this.bound_ ) this.unbindData(old);
        if ( nu ) this.bindData(nu);
      }
    },
    {
      name: 'childData'
    },
    {
      name: 'innerView',
      help: 'Override for prop.view',
      documentation: function() {/*
        The optional name of the desired sub-$$DOC{ref:'foam.ui.View'}. If not specified,
        prop.$$DOC{ref:'Property.view'} is used. DEPRECATED. Use $$DOC{ref:'.model'} instead.
      */},
      postSet: function(old,nu) {
        this.model = nu;
      }
    },
    {
      name: 'view',
      type: 'foam.ui.View',
      adapt: function(_, v) { return v && v.toView_ ? v.toView_() : v; },
      documentation: function() {/*
        The new sub-$$DOC{ref:'foam.ui.View'} generated for the given $$DOC{ref:'Property'}.
      */}
    },
    {
      name: 'bound_',
      model_: 'BooleanProperty',
      defaultValue: false
    }
  ],

  methods: {
    unbindData: function(oldData) {
      if ( ! this.bound_ || ! oldData || ! this.prop ) return;
      var pValue = oldData.propertyValue(this.prop.name);
      Events.unlink(pValue, this.childData$);
      this.bound_ = false;
    },

    bindData: function(data) {
      if ( this.bound_ || ! data || ! this.prop) return;
      var pValue = data.propertyValue(this.prop.name);
      Events.link(pValue, this.childData$);
      this.bound_ = true;
    },

    toString: function() { /* Name info. */ return 'PropertyView(' + this.prop.name + ', ' + this.view + ')'; },

    destroy: function( isParentDestroyed ) { /* Passthrough to $$DOC{ref:'.view'} */
      // always unbind, since if our parent was the top level destroy we need
      // to unhook if we were created as an addSelfDataChild
      this.unbindData(this.data);
      this.SUPER( isParentDestroyed );
    },

    construct: function() {
      // if not bound yet and we do have data set, bind it
      this.bindData(this.data);
      this.SUPER();
    },

    finishRender: function() {
      this.SUPER();
      this.view.prop = this.prop;
    },

    addDataChild: function(child) {
      Events.link(this.childData$, child.data$);
      this.addChild(child);
    }
  }

});
<|endoftext|>


<commit_before>System.register('flarum/sticky/addStickyBadge', ['flarum/extend', 'flarum/models/Discussion', 'flarum/components/Badge'], function (_export) {
  'use strict';

  var extend, Discussion, Badge;

  _export('default', addStickyBadge);

  function addStickyBadge() {
    extend(Discussion.prototype, 'badges', function (badges) {
      if (this.isSticky()) {
        badges.add('sticky', Badge.component({
          type: 'sticky',
          label: app.translator.trans('flarum-sticky.forum.badge.sticky_tooltip'),
          icon: 'thumb-tack'
        }), 10);
      }
    });
  }

  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
    }, function (_flarumModelsDiscussion) {
      Discussion = _flarumModelsDiscussion['default'];
    }, function (_flarumComponentsBadge) {
      Badge = _flarumComponentsBadge['default'];
    }],
    execute: function () {}
  };
});;System.register('flarum/sticky/addStickyControl', ['flarum/extend', 'flarum/utils/DiscussionControls', 'flarum/components/DiscussionPage', 'flarum/components/Button'], function (_export) {
  'use strict';

  var extend, DiscussionControls, DiscussionPage, Button;

  _export('default', addStickyControl);

  function addStickyControl() {
    extend(DiscussionControls, 'moderationControls', function (items, discussion) {
      if (discussion.canSticky()) {
        items.add('sticky', Button.component({
          children: app.translator.trans(discussion.isSticky() ? 'flarum-sticky.forum.discussion_controls.unsticky_button' : 'flarum-sticky.forum.discussion_controls.sticky_button'),
          icon: 'thumb-tack',
          onclick: this.stickyAction.bind(discussion)
        }));
      }
    });

    DiscussionControls.stickyAction = function () {
      this.save({ isSticky: !this.isSticky() }).then(function () {
        if (app.current instanceof DiscussionPage) {
          app.current.stream.update();
        }

        m.redraw();
      });
    };
  }

  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
    }, function (_flarumUtilsDiscussionControls) {
      DiscussionControls = _flarumUtilsDiscussionControls['default'];
    }, function (_flarumComponentsDiscussionPage) {
      DiscussionPage = _flarumComponentsDiscussionPage['default'];
    }, function (_flarumComponentsButton) {
      Button = _flarumComponentsButton['default'];
    }],
    execute: function () {}
  };
});;System.register('flarum/sticky/addStickyExcerpt', ['flarum/extend', 'flarum/components/DiscussionList', 'flarum/components/DiscussionListItem', 'flarum/utils/string'], function (_export) {
  'use strict';

  var extend, DiscussionList, DiscussionListItem, truncate;

  _export('default', addStickyControl);

  function addStickyControl() {
    extend(DiscussionList.prototype, 'requestParams', function (params) {
      params.include.push('startPost');
    });

    extend(DiscussionListItem.prototype, 'infoItems', function (items) {
      var discussion = this.props.discussion;

      if (discussion.isSticky()) {
        var startPost = discussion.startPost();

        if (startPost) {
          var excerpt = m(
            'span',
            null,
            truncate(startPost.contentPlain(), 200)
          );

          items.add('excerpt', excerpt, -100);
        }
      }
    });
  }

  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
    }, function (_flarumComponentsDiscussionList) {
      DiscussionList = _flarumComponentsDiscussionList['default'];
    }, function (_flarumComponentsDiscussionListItem) {
      DiscussionListItem = _flarumComponentsDiscussionListItem['default'];
    }, function (_flarumUtilsString) {
      truncate = _flarumUtilsString.truncate;
    }],
    execute: function () {}
  };
});;System.register('flarum/sticky/main', ['flarum/extend', 'flarum/app', 'flarum/Model', 'flarum/models/Discussion', 'flarum/sticky/components/DiscussionStickiedPost', 'flarum/sticky/addStickyBadge', 'flarum/sticky/addStickyControl', 'flarum/sticky/addStickyExcerpt'], function (_export) {
  'use strict';

  var extend, notificationType, app, Model, Discussion, DiscussionStickiedPost, addStickyBadge, addStickyControl, addStickyExcerpt;
  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
      notificationType = _flarumExtend.notificationType;
    }, function (_flarumApp) {
      app = _flarumApp['default'];
    }, function (_flarumModel) {
      Model = _flarumModel['default'];
    }, function (_flarumModelsDiscussion) {
      Discussion = _flarumModelsDiscussion['default'];
    }, function (_flarumStickyComponentsDiscussionStickiedPost) {
      DiscussionStickiedPost = _flarumStickyComponentsDiscussionStickiedPost['default'];
    }, function (_flarumStickyAddStickyBadge) {
      addStickyBadge = _flarumStickyAddStickyBadge['default'];
    }, function (_flarumStickyAddStickyControl) {
      addStickyControl = _flarumStickyAddStickyControl['default'];
    }, function (_flarumStickyAddStickyExcerpt) {
      addStickyExcerpt = _flarumStickyAddStickyExcerpt['default'];
    }],
    execute: function () {

      app.initializers.add('flarum-sticky', function () {
        app.postComponents.discussionStickied = DiscussionStickiedPost;

        Discussion.prototype.isSticky = Model.attribute('isSticky');
        Discussion.prototype.canSticky = Model.attribute('canSticky');

        addStickyBadge();
        addStickyControl();
        addStickyExcerpt();
      });
    }
  };
});;System.register('flarum/sticky/components/DiscussionStickiedPost', ['flarum/components/EventPost'], function (_export) {
  'use strict';

  var EventPost, DiscussionStickiedPost;
  return {
    setters: [function (_flarumComponentsEventPost) {
      EventPost = _flarumComponentsEventPost['default'];
    }],
    execute: function () {
      DiscussionStickiedPost = (function (_EventPost) {
        babelHelpers.inherits(DiscussionStickiedPost, _EventPost);

        function DiscussionStickiedPost() {
          babelHelpers.classCallCheck(this, DiscussionStickiedPost);
          babelHelpers.get(Object.getPrototypeOf(DiscussionStickiedPost.prototype), 'constructor', this).apply(this, arguments);
        }

        babelHelpers.createClass(DiscussionStickiedPost, [{
          key: 'icon',
          value: function icon() {
            return 'thumb-tack';
          }
        }, {
          key: 'descriptionKey',
          value: function descriptionKey() {
            return this.props.post.content().sticky ? 'flarum-sticky.forum.post_stream.discussion_stickied_text' : 'flarum-sticky.forum.post_stream.discussion_unstickied_text';
          }
        }]);
        return DiscussionStickiedPost;
      })(EventPost);

      _export('default', DiscussionStickiedPost);
    }
  };
});<commit_msg>Release 0.1.0-beta.3<commit_after>System.register('flarum/sticky/addStickyBadge', ['flarum/extend', 'flarum/models/Discussion', 'flarum/components/Badge'], function (_export) {
  'use strict';

  var extend, Discussion, Badge;

  _export('default', addStickyBadge);

  function addStickyBadge() {
    extend(Discussion.prototype, 'badges', function (badges) {
      if (this.isSticky()) {
        badges.add('sticky', Badge.component({
          type: 'sticky',
          label: app.translator.trans('flarum-sticky.forum.badge.sticky_tooltip'),
          icon: 'thumb-tack'
        }), 10);
      }
    });
  }

  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
    }, function (_flarumModelsDiscussion) {
      Discussion = _flarumModelsDiscussion['default'];
    }, function (_flarumComponentsBadge) {
      Badge = _flarumComponentsBadge['default'];
    }],
    execute: function () {}
  };
});;
System.register('flarum/sticky/addStickyControl', ['flarum/extend', 'flarum/utils/DiscussionControls', 'flarum/components/DiscussionPage', 'flarum/components/Button'], function (_export) {
  'use strict';

  var extend, DiscussionControls, DiscussionPage, Button;

  _export('default', addStickyControl);

  function addStickyControl() {
    extend(DiscussionControls, 'moderationControls', function (items, discussion) {
      if (discussion.canSticky()) {
        items.add('sticky', Button.component({
          children: app.translator.trans(discussion.isSticky() ? 'flarum-sticky.forum.discussion_controls.unsticky_button' : 'flarum-sticky.forum.discussion_controls.sticky_button'),
          icon: 'thumb-tack',
          onclick: this.stickyAction.bind(discussion)
        }));
      }
    });

    DiscussionControls.stickyAction = function () {
      this.save({ isSticky: !this.isSticky() }).then(function () {
        if (app.current instanceof DiscussionPage) {
          app.current.stream.update();
        }

        m.redraw();
      });
    };
  }

  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
    }, function (_flarumUtilsDiscussionControls) {
      DiscussionControls = _flarumUtilsDiscussionControls['default'];
    }, function (_flarumComponentsDiscussionPage) {
      DiscussionPage = _flarumComponentsDiscussionPage['default'];
    }, function (_flarumComponentsButton) {
      Button = _flarumComponentsButton['default'];
    }],
    execute: function () {}
  };
});;
System.register('flarum/sticky/addStickyExcerpt', ['flarum/extend', 'flarum/components/DiscussionList', 'flarum/components/DiscussionListItem', 'flarum/utils/string'], function (_export) {
  'use strict';

  var extend, DiscussionList, DiscussionListItem, truncate;

  _export('default', addStickyControl);

  function addStickyControl() {
    extend(DiscussionList.prototype, 'requestParams', function (params) {
      params.include.push('startPost');
    });

    extend(DiscussionListItem.prototype, 'infoItems', function (items) {
      var discussion = this.props.discussion;

      if (discussion.isSticky()) {
        var startPost = discussion.startPost();

        if (startPost) {
          var excerpt = m(
            'span',
            null,
            truncate(startPost.contentPlain(), 200)
          );

          items.add('excerpt', excerpt, -100);
        }
      }
    });
  }

  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
    }, function (_flarumComponentsDiscussionList) {
      DiscussionList = _flarumComponentsDiscussionList['default'];
    }, function (_flarumComponentsDiscussionListItem) {
      DiscussionListItem = _flarumComponentsDiscussionListItem['default'];
    }, function (_flarumUtilsString) {
      truncate = _flarumUtilsString.truncate;
    }],
    execute: function () {}
  };
});;
System.register('flarum/sticky/components/DiscussionStickiedPost', ['flarum/components/EventPost'], function (_export) {
  'use strict';

  var EventPost, DiscussionStickiedPost;
  return {
    setters: [function (_flarumComponentsEventPost) {
      EventPost = _flarumComponentsEventPost['default'];
    }],
    execute: function () {
      DiscussionStickiedPost = (function (_EventPost) {
        babelHelpers.inherits(DiscussionStickiedPost, _EventPost);

        function DiscussionStickiedPost() {
          babelHelpers.classCallCheck(this, DiscussionStickiedPost);
          babelHelpers.get(Object.getPrototypeOf(DiscussionStickiedPost.prototype), 'constructor', this).apply(this, arguments);
        }

        babelHelpers.createClass(DiscussionStickiedPost, [{
          key: 'icon',
          value: function icon() {
            return 'thumb-tack';
          }
        }, {
          key: 'descriptionKey',
          value: function descriptionKey() {
            return this.props.post.content().sticky ? 'flarum-sticky.forum.post_stream.discussion_stickied_text' : 'flarum-sticky.forum.post_stream.discussion_unstickied_text';
          }
        }]);
        return DiscussionStickiedPost;
      })(EventPost);

      _export('default', DiscussionStickiedPost);
    }
  };
});;
System.register('flarum/sticky/main', ['flarum/extend', 'flarum/app', 'flarum/Model', 'flarum/models/Discussion', 'flarum/sticky/components/DiscussionStickiedPost', 'flarum/sticky/addStickyBadge', 'flarum/sticky/addStickyControl', 'flarum/sticky/addStickyExcerpt'], function (_export) {
  'use strict';

  var extend, notificationType, app, Model, Discussion, DiscussionStickiedPost, addStickyBadge, addStickyControl, addStickyExcerpt;
  return {
    setters: [function (_flarumExtend) {
      extend = _flarumExtend.extend;
      notificationType = _flarumExtend.notificationType;
    }, function (_flarumApp) {
      app = _flarumApp['default'];
    }, function (_flarumModel) {
      Model = _flarumModel['default'];
    }, function (_flarumModelsDiscussion) {
      Discussion = _flarumModelsDiscussion['default'];
    }, function (_flarumStickyComponentsDiscussionStickiedPost) {
      DiscussionStickiedPost = _flarumStickyComponentsDiscussionStickiedPost['default'];
    }, function (_flarumStickyAddStickyBadge) {
      addStickyBadge = _flarumStickyAddStickyBadge['default'];
    }, function (_flarumStickyAddStickyControl) {
      addStickyControl = _flarumStickyAddStickyControl['default'];
    }, function (_flarumStickyAddStickyExcerpt) {
      addStickyExcerpt = _flarumStickyAddStickyExcerpt['default'];
    }],
    execute: function () {

      app.initializers.add('flarum-sticky', function () {
        app.postComponents.discussionStickied = DiscussionStickiedPost;

        Discussion.prototype.isSticky = Model.attribute('isSticky');
        Discussion.prototype.canSticky = Model.attribute('canSticky');

        addStickyBadge();
        addStickyControl();
        addStickyExcerpt();
      });
    }
  };
});<|endoftext|>


<commit_before>/* ========================================================================
 * Bootstrap (plugin): validator.js v0.8.1
 * ========================================================================
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Cina Saffary.
 * Made by @1000hz in the style of Bootstrap 3 era @fat
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * ======================================================================== */

+function ($) {
  'use strict';

  var inputSelector = ':input:not([type="submit"], button):enabled:visible,.input-custom'
  // VALIDATOR CLASS DEFINITION
  // ==========================

  var Validator = function (element, options) {
    this.$element = $(element)
    this.options  = options

    options.errors = $.extend({}, Validator.DEFAULTS.errors, options.errors)

    for (var custom in options.custom) {
      if (!options.errors[custom]) throw new Error('Missing default error message for custom validator: ' + custom)
    }

    $.extend(Validator.VALIDATORS, options.custom)

    this.$element.attr('novalidate', true) // disable automatic native validation
    this.toggleSubmit()

    this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.validateInput, this))
    this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this))

    this.$element.find('[data-match]').each(function () {
      var $this  = $(this)
      var target = $this.data('match')

      $(target).on('input.bs.validator', function (e) {
        $this.val() && $this.trigger('input.bs.validator')
      })
    })
  }

  Validator.DEFAULTS = {
    delay: 500,
    html: false,
    disable: true,
    custom: {},
    errors: {
      match: 'Does not match',
      minlength: 'Not long enough'
    }
  }

  Validator.VALIDATORS = {
    native: function ($el) {
      var el = $el[0]
      return el.checkValidity ? el.checkValidity() : true
    },
    match: function ($el) {
      var target = $el.data('match')
      return !$el.val() || $el.val() === $(target).val()
    },
    minlength: function ($el) {
      var minlength = $el.data('minlength')
      return !$el.val() || $el.val().length >= minlength
    },
    required: function($el) {
      if ($el.is('[required]')) {
        return $el.val() && $el.val() !== '';
      }
      return true;
    }
  }

  Validator.prototype.validateInput = function (e) {
    var self       = this;
    var $el        = $(e.target)
    var prevErrors = $el.data('bs.validator.errors')
    var errors

    /**
     * [if custom input element]
     * @param  {[type]} #el.is('.input-custom') [description]
     * @return {[type]}                         [description]
     */

    if (!$el.is(inputSelector) && !$el.is('input')) {
      $el = $el.parents('.input-custom');
    }

    if ($el.length < 1) {
      return
    }

    if ($el.is('.input-custom')) {
      $el.val = function() {
        return $el.data('value');
      }
      setTimeout(doValidate, 5);
      return
    }

    if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]')

    setTimeout(doValidate, 5);

    function doValidate() {
      self.$element.trigger(e = $.Event('validate.bs.validator', {relatedTarget: $el[0]}))
      if (e.isDefaultPrevented()) return

      self.runValidators($el).done(function (errors) {
        $el.data('bs.validator.errors', errors)

        errors.length ? self.showErrors($el) : self.clearErrors($el)

        if (!prevErrors || errors.toString() !== prevErrors.toString()) {
          e = errors.length
            ? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors})
            : $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors})

          self.$element.trigger(e)
        }

        self.toggleSubmit()

        self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]}))
      })
    }
  }

  Validator.prototype.runValidators = function ($el) {
    var errors   = []
    var deferred = $.Deferred()
    var options  = this.options

    $el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject()
    $el.data('bs.validator.deferred', deferred)

    function getErrorMessage(key) {
      return $el.data(key + '-error')
        || $el.data('error')
        || key == 'native' && $el[0].validationMessage
        || options.errors[key]
    }

    $.each(Validator.VALIDATORS, $.proxy(function (key, validator) {
      if (($el.data(key) || key == 'required' || key == 'native') && !validator.call(this, $el)) {
        var error = getErrorMessage(key)
        !~errors.indexOf(error) && errors.push(error)
      }
    }, this))

    if (!errors.length && $el.val() && $el.data('remote')) {
      this.defer($el, function () {
        var data = {}
        data[$el.attr('name')] = $el.val()
        $.get($el.data('remote'), data)
          .fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) })
          .always(function () { deferred.resolve(errors)})
      })
    } else deferred.resolve(errors)

    return deferred.promise()
  }

  Validator.prototype.validate = function () {
    var delay = this.options.delay

    this.options.delay = 0
    this.$element.find(inputSelector).trigger('input.bs.validator')
    this.options.delay = delay

    return this
  }

  Validator.prototype.showErrors = function ($el) {
    var method = this.options.html ? 'html' : 'text'

    this.defer($el, function () {
      var $group = $el.closest('.form-group')
      var $block = $group.find('.help-block.with-errors')
      var $feedback = $group.find('.form-control-feedback')
      var errors = $el.data('bs.validator.errors')

      if (!errors.length) return

      errors = $('<ul/>')
        .addClass('list-unstyled')
        .append($.map(errors, function (error) { return $('<li/>')[method](error) }))

      $block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html())
      $block.empty().append(errors)
      $group.addClass('has-error')

      $feedback.length
        && $feedback.removeClass('glyphicon-ok')
        && $feedback.addClass('glyphicon-warning-sign')
        && $group.removeClass('has-success')
    })
  }

  Validator.prototype.clearErrors = function ($el) {
    var $group = $el.closest('.form-group')
    var $block = $group.find('.help-block.with-errors')
    var $feedback = $group.find('.form-control-feedback')

    $block.html($block.data('bs.validator.originalContent'))
    $group.removeClass('has-error')

    $feedback.length
      && $feedback.removeClass('glyphicon-warning-sign')
      && $feedback.addClass('glyphicon-ok')
      && $group.addClass('has-success')
  }

  Validator.prototype.hasErrors = function () {
    function fieldErrors() {
      return !!($(this).data('bs.validator.errors') || []).length
    }

    return !!this.$element.find(inputSelector).filter(fieldErrors).length
  }

  Validator.prototype.isIncomplete = function () {
    function fieldIncomplete() {
      return this.type === 'checkbox' ? !this.checked                                   :
             this.type === 'radio'    ? !$('[name="' + this.name + '"]:checked').length :
                                        $.trim(this.value) === ''
    }

    return !!this.$element.find(inputSelector).filter('[required]').filter(fieldIncomplete).length
  }

  Validator.prototype.onSubmit = function (e) {
    this.validate()
    if (this.isIncomplete() || this.hasErrors()) e.preventDefault()
  }

  Validator.prototype.toggleSubmit = function () {
    if(!this.options.disable) return
    var $btn = $('button[type="submit"], input[type="submit"]')
      .filter('[form="' + this.$element.attr('id') + '"]')
      .add(this.$element.find('input[type="submit"], button[type="submit"]'))
    $btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors())
      .css({'pointer-events': 'all', 'cursor': 'pointer'})
  }

  Validator.prototype.defer = function ($el, callback) {
    if (!this.options.delay) return callback()
    window.clearTimeout($el.data('bs.validator.timeout'))
    $el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay))
  }

  Validator.prototype.destroy = function () {
    this.$element
      .removeAttr('novalidate')
      .removeData('bs.validator')
      .off('.bs.validator')

    this.$element.find(inputSelector)
      .off('.bs.validator')
      .removeData(['bs.validator.errors', 'bs.validator.deferred'])
      .each(function () {
        var $this = $(this)
        var timeout = $this.data('bs.validator.timeout')
        window.clearTimeout(timeout) && $this.removeData('bs.validator.timeout')
      })

    this.$element.find('.help-block.with-errors').each(function () {
      var $this = $(this)
      var originalContent = $this.data('bs.validator.originalContent')

      $this
        .removeData('bs.validator.originalContent')
        .html(originalContent)
    })

    this.$element.find('input[type="submit"], button[type="submit"]').removeClass('disabled')

    this.$element.find('.has-error').removeClass('has-error')

    return this
  }

  // VALIDATOR PLUGIN DEFINITION
  // ===========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option)
      var data    = $this.data('bs.validator')

      if (!data && option == 'destroy') return
      if (!data) $this.data('bs.validator', (data = new Validator(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.validator

  $.fn.validator             = Plugin
  $.fn.validator.Constructor = Validator

  // VALIDATOR NO CONFLICT
  // =====================

  $.fn.validator.noConflict = function () {
    $.fn.validator = old
    return this
  }

  // VALIDATOR DATA-API
  // ==================

  $(window).on('load', function () {
    $('form[data-toggle="validator"]').each(function () {
      var $form = $(this)
      Plugin.call($form, $form.data())
    })
  })

}(jQuery);
<commit_msg>延时会对submit影响<commit_after>/* ========================================================================
 * Bootstrap (plugin): validator.js v0.8.1
 * ========================================================================
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Cina Saffary.
 * Made by @1000hz in the style of Bootstrap 3 era @fat
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * ======================================================================== */

+function ($) {
  'use strict';

  var inputSelector = ':input:not([type="submit"], button):enabled:visible,.input-custom'
  var nativeInputSelector =  ':input:not([type="submit"], button):enabled:visible'
  // VALIDATOR CLASS DEFINITION
  // ==========================

  var Validator = function (element, options) {
    this.$element = $(element)
    this.options  = options

    options.errors = $.extend({}, Validator.DEFAULTS.errors, options.errors)

    for (var custom in options.custom) {
      if (!options.errors[custom]) throw new Error('Missing default error message for custom validator: ' + custom)
    }

    $.extend(Validator.VALIDATORS, options.custom)

    this.$element.attr('novalidate', true) // disable automatic native validation
    this.toggleSubmit()

    this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.validateInput, this))
    this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this))

    this.$element.find('[data-match]').each(function () {
      var $this  = $(this)
      var target = $this.data('match')

      $(target).on('input.bs.validator', function (e) {
        $this.val() && $this.trigger('input.bs.validator')
      })
    })
  }

  Validator.DEFAULTS = {
    delay: 500,
    html: false,
    disable: true,
    custom: {},
    errors: {
      match: 'Does not match',
      minlength: 'Not long enough'
    }
  }

  Validator.VALIDATORS = {
    native: function ($el) {
      var el = $el[0]
      return el.checkValidity ? el.checkValidity() : true
    },
    match: function ($el) {
      var target = $el.data('match')
      return !$el.val() || $el.val() === $(target).val()
    },
    minlength: function ($el) {
      var minlength = $el.data('minlength')
      return !$el.val() || $el.val().length >= minlength
    },
    required: function($el) {
      if ($el.is('[required]')) {
        return $el.val() && $el.val() !== '';
      }
      return true;
    }
  }

  Validator.prototype.validateInput = function (e) {
    var self       = this;
    var $el        = $(e.target)
    var prevErrors = $el.data('bs.validator.errors')
    var errors

    /**
     * [if custom input element]
     * @param  {[type]} #el.is('.input-custom') [description]
     * @return {[type]}                         [description]
     */

    if (!$el.is(inputSelector) && !$el.is('input')) {
      $el = $el.parents('.input-custom');
    }

    if ($el.length < 1) {
      return
    }

    if ($el.is('.input-custom')) {
      $el.val = function() {
        return $el.data('value');
      }
      doValidate();
      return
    }

    if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]')

    doValidate();

    function doValidate() {
      self.$element.trigger(e = $.Event('validate.bs.validator', {relatedTarget: $el[0]}))
      if (e.isDefaultPrevented()) return

      self.runValidators($el).done(function (errors) {
        $el.data('bs.validator.errors', errors)

        errors.length ? self.showErrors($el) : self.clearErrors($el)

        if (!prevErrors || errors.toString() !== prevErrors.toString()) {
          e = errors.length
            ? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors})
            : $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors})

          self.$element.trigger(e)
        }

        self.toggleSubmit()

        self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]}))
      })
    }
  }

  Validator.prototype.runValidators = function ($el) {
    var errors   = []
    var deferred = $.Deferred()
    var options  = this.options

    $el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject()
    $el.data('bs.validator.deferred', deferred)

    function getErrorMessage(key) {
      return $el.data(key + '-error')
        || $el.data('error')
        || key == 'native' && $el[0].validationMessage
        || options.errors[key]
    }

    $.each(Validator.VALIDATORS, $.proxy(function (key, validator) {
      if (($el.data(key) || key == 'required' || key == 'native') && !validator.call(this, $el)) {
        var error = getErrorMessage(key)
        !~errors.indexOf(error) && errors.push(error)
      }
    }, this))

    if (!errors.length && $el.val() && $el.data('remote')) {
      this.defer($el, function () {
        var data = {}
        data[$el.attr('name')] = $el.val()
        $.get($el.data('remote'), data)
          .fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) })
          .always(function () { deferred.resolve(errors)})
      })
    } else deferred.resolve(errors)

    return deferred.promise()
  }

  Validator.prototype.validate = function () {
    var delay = this.options.delay

    this.options.delay = 0
    this.$element.find(inputSelector).trigger('input.bs.validator')
    this.options.delay = delay

    return this
  }

  Validator.prototype.showErrors = function ($el) {
    var method = this.options.html ? 'html' : 'text'

    this.defer($el, function () {
      var $group = $el.closest('.form-group')
      var $block = $group.find('.help-block.with-errors')
      var $feedback = $group.find('.form-control-feedback')
      var errors = $el.data('bs.validator.errors')

      if (!errors.length) return

      errors = $('<ul/>')
        .addClass('list-unstyled')
        .append($.map(errors, function (error) { return $('<li/>')[method](error) }))

      $block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html())
      $block.empty().append(errors)
      $group.addClass('has-error')

      $feedback.length
        && $feedback.removeClass('glyphicon-ok')
        && $feedback.addClass('glyphicon-warning-sign')
        && $group.removeClass('has-success')
    })
  }

  Validator.prototype.clearErrors = function ($el) {
    var $group = $el.closest('.form-group')
    var $block = $group.find('.help-block.with-errors')
    var $feedback = $group.find('.form-control-feedback')

    $block.html($block.data('bs.validator.originalContent'))
    $group.removeClass('has-error')

    $feedback.length
      && $feedback.removeClass('glyphicon-warning-sign')
      && $feedback.addClass('glyphicon-ok')
      && $group.addClass('has-success')
  }

  Validator.prototype.hasErrors = function () {
    function fieldErrors() {
      return !!($(this).data('bs.validator.errors') || []).length
    }

    return !!this.$element.find(inputSelector).filter(fieldErrors).length
  }

  Validator.prototype.isIncomplete = function () {
    function fieldIncomplete() {
      return this.type === 'checkbox' ? !this.checked                                   :
             this.type === 'radio'    ? !$('[name="' + this.name + '"]:checked').length :
                                        $.trim(this.value) === ''
    }

    return !!this.$element.find(nativeInputSelector).filter('[required]').filter(fieldIncomplete).length
  }

  Validator.prototype.onSubmit = function (e) {
    this.validate()
    if (this.isIncomplete() || this.hasErrors()) e.preventDefault()
  }

  Validator.prototype.toggleSubmit = function () {
    if(!this.options.disable) return
    var $btn = $('button[type="submit"], input[type="submit"]')
      .filter('[form="' + this.$element.attr('id') + '"]')
      .add(this.$element.find('input[type="submit"], button[type="submit"]'))
    $btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors())
      .css({'pointer-events': 'all', 'cursor': 'pointer'})
  }

  Validator.prototype.defer = function ($el, callback) {
    if (!this.options.delay) return callback()
    window.clearTimeout($el.data('bs.validator.timeout'))
    $el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay))
  }

  Validator.prototype.destroy = function () {
    this.$element
      .removeAttr('novalidate')
      .removeData('bs.validator')
      .off('.bs.validator')

    this.$element.find(inputSelector)
      .off('.bs.validator')
      .removeData(['bs.validator.errors', 'bs.validator.deferred'])
      .each(function () {
        var $this = $(this)
        var timeout = $this.data('bs.validator.timeout')
        window.clearTimeout(timeout) && $this.removeData('bs.validator.timeout')
      })

    this.$element.find('.help-block.with-errors').each(function () {
      var $this = $(this)
      var originalContent = $this.data('bs.validator.originalContent')

      $this
        .removeData('bs.validator.originalContent')
        .html(originalContent)
    })

    this.$element.find('input[type="submit"], button[type="submit"]').removeClass('disabled')

    this.$element.find('.has-error').removeClass('has-error')

    return this
  }

  // VALIDATOR PLUGIN DEFINITION
  // ===========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option)
      var data    = $this.data('bs.validator')

      if (!data && option == 'destroy') return
      if (!data) $this.data('bs.validator', (data = new Validator(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.validator

  $.fn.validator             = Plugin
  $.fn.validator.Constructor = Validator

  // VALIDATOR NO CONFLICT
  // =====================

  $.fn.validator.noConflict = function () {
    $.fn.validator = old
    return this
  }

  // VALIDATOR DATA-API
  // ==================

  $(window).on('load', function () {
    $('form[data-toggle="validator"]').each(function () {
      var $form = $(this)
      Plugin.call($form, $form.data())
    })
  })

}(jQuery);
<|endoftext|>


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

/**
 * Gets configuration information that is required by the 'build' task.
 * Reads information from multiple places, including:
 *
 * chipper/build.json
 * $HOME/.phet/build-local.json
 * REPO/package.json
 * grunt.options( optionName )
 *
 * All fields are @public (read-only).
 * Fields include:
 *
 * {string} name - name of the repository being built
 * {string} version - version identifier
 * {string} license - license
 * {string} simTitleStringKey - key of the sim's title string
 * {string} requirejsNamespace - the repository's requirejs namespace in config.js, eg AREA_BUILDER
 * {string} brand - brand identifier
 * {string[]} phetLibs - other repositories that are required by the sim
 * {string[]} preload - scripts that need to be preloaded in the .html file, in the order that they will be preloaded
 * {string[]} licenseKeys - keys to licenses in sherpa/lib/license.json, for third-party dependencies
 * {string[]} locales - locales to build
 * {string[]} availableLocales - locales for which translations are available
 * {Object} gruntConfig
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
/* eslint-env node */
'use strict';

// built-in node APIs
var assert = require( 'assert' );

// 3rd-party packages
var _ = require( '../../../sherpa/lib/lodash-2.4.1.min' ); // eslint-disable-line require-statement-match

// modules
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );
var getVersionForBrand = require( '../../../chipper/js/getVersionForBrand' );
var getBrand = require( '../../../chipper/js/grunt/getBrand' );

/**
 * @param {Object} grunt - the grunt instance
 */
module.exports = function( grunt ) {

  /**
   * Gets phetLibs, the set of repositories on which the repository being built depends.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPhetLibs( packageJSON, buildJSON, brand ) {

    // start with package.json
    var phetLibs = packageJSON.phet.phetLibs || [];

    // add the repo that's being built
    phetLibs.push( packageJSON.name );

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].phetLibs ) {
        phetLibs = phetLibs.concat( buildJSON[ id ].phetLibs );
      }
    } );

    // sort and remove duplicates
    return _.uniq( phetLibs.sort() );
  }

  /**
   * Gets preload, the set of scripts to be preloaded in the .html file.
   * NOTE! Order of the return value is significant, since it corresponds to the order in which scripts will be preloaded.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPreload( packageJSON, buildJSON, brand ) {

    var preload = [];

    // add preloads that are common to all sims, from build.json
    if ( buildJSON.common && buildJSON.common.preload ) {
      preload = preload.concat( buildJSON.common.preload );
    }

    // add sim-specific preloads from package.json
    if ( packageJSON.phet.preload ) {
      preload = preload.concat( packageJSON.phet.preload );
    }

    // add brand-specific preloads from build.json
    if ( buildJSON[ brand ] && buildJSON[ brand ].preload ) {
      preload = preload.concat( buildJSON[ brand ].preload );
    }

    // add brand-specific preloads from package.json
    if ( packageJSON.phet[ brand ] && packageJSON.phet[ brand ].preload ) {
      preload = preload.concat( packageJSON.phet[ brand ].preload );
    }

    // remove duplicates (do NOT sort, order is significant!)
    return _.uniq( preload );
  }

  /**
   * Used for retrieving brand-specific preload
   * @param buildJSON
   * @param brand
   * @returns {string[]} - empty array if there are no preloads
   */
  function getPreloadForBrand( buildJSON, brand ) {
    var preload = [];

    // add brand-specific preloads from build.json
    if ( buildJSON[ brand ] && buildJSON[ brand ].preload ) {
      preload = buildJSON[ brand ].preload;
    }

    if ( packageJSON.phet[ brand ] && packageJSON.phet[ brand ].preload ) {
      preload = preload.concat( packageJSON.phet[ brand ].preload );
    }
    console.log( preload );
    return preload;
  }

  /**
   * Gets the license keys for sherpa (third-party) libs that are used.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @param {string[]} preload
   * @returns {string[]}
   */
  function getLicenseKeys( packageJSON, buildJSON, brand, preload ) {

    // start with package.json
    var licenseKeys = packageJSON.phet.licenseKeys || [];

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].licenseKeys ) {
        licenseKeys = licenseKeys.concat( buildJSON[ id ].licenseKeys );
      }
    } );

    // Extract keys from preload for sherpa (third-party) dependencies
    preload.forEach( function( path ) {
      if ( path.indexOf( '/sherpa/' ) !== -1 ) {
        var lastSlash = path.lastIndexOf( '/' );
        var key = path.substring( lastSlash + 1 );
        licenseKeys.push( key );
      }
    } );

    // sort and remove duplicates
    return _.uniq( _.sortBy( licenseKeys, function( key ) { return key.toUpperCase(); } ) );
  }

  /*
   * Gets the locales from a repository, by inspecting the names of the string files in babel for that repository.
   *
   * @param {string} repository - name of the repository to get locales from
   */
  function getLocalesFromRepository( repository ) {

    // confirm that the repository has a strings directory
    var stringsDirectory = '../babel/' + repository;
    assert( grunt.file.isDir(), stringsDirectory + 'is not a directory' );

    // Get names of string files.
    var stringFiles = grunt.file.expand( stringsDirectory + '/' + repository + '-strings_*.json' );

    // Don't fail out if there are no string files, as this is a normal condition when building new simulations
    if ( stringFiles.length === 0 ) {
      grunt.log.debug( 'No string files found in ' + stringsDirectory + ' for repository ' + repository );
      return [];
    }

    // Extract the locales from the file names.
    // File names must have a form like 'graphing-lines-strings_ar_SA.json', where no '_' appear in the repo name.
    var locales = stringFiles.map( function( filename ) {
      return filename.substring( filename.indexOf( '_' ) + 1, filename.lastIndexOf( '.' ) );
    } );
    assert( locales.length > 0, 'no locales found in ' + stringsDirectory );

    return locales;
  }

  /**
   * Gets the set of locales to be built.
   *
   * The grunt options are:
   *
   * --locales=* : all locales from the repo's strings/ directory
   * --locales=fr : French
   * --locales=ar,fr,es : Arabic, French and Spanish (comma-separated locales)
   * --localesRepo=beers-law-lab: all locales from another repository's strings/ directory, ignored if --locales is specified
   *
   * @param {Object} grunt - the grunt instance
   * @param {string} repository - name of the repository that is being built
   */
  function getLocales( grunt, repository ) {

    var locales = [ ChipperConstants.FALLBACK_LOCALE ];
    var localesValue = grunt.option( 'locales' );

    if ( localesValue ) {
      if ( localesValue === '*' ) {
        locales = locales.concat( getLocalesFromRepository( repository ) ); // all locales for the repository that we're building
      }
      else {
        // use only the specified locales, which may not include the fallback
        locales = localesValue.split( ',' );
      }
    }
    else {
      var localesRepo = grunt.option( 'localesRepo' );
      if ( localesRepo ) {
        locales = locales.concat( getLocalesFromRepository( localesRepo ) ); // all locales for some other repository
      }
    }

    return _.uniq( locales.sort() );
  }

  /**
   * Verifies that preload repositories are included in phetLib.
   *
   * @param {string[]} preload
   * @param {string[]} phetLibs
   */
  function validatePreload( preload, phetLibs ) {
    console.log( phetLibs );
    var missingRepositories = [];
    preload.forEach( function( entry ) {

      // preload entries should start with '..', e.g. "../assert/js/assert.js"
      assert( entry.split( '/' )[ 0 ] === '..', 'malformed preload entry: ' + entry );

      // the preload's repository should be in phetLib
      var repositoryName = entry.split( '/' )[ 1 ];
      if ( phetLibs.indexOf( repositoryName ) === -1 && missingRepositories.indexOf( repositoryName ) === -1 ) {
        missingRepositories.push( repositoryName );
      }
    } );
    assert( missingRepositories.length === 0,
      'phetLib is missing repositories required by preload: ' + missingRepositories.toString() );
  }

  //------------------------------------------------------------------------------------
  // read configuration files

  // ./package.json (required)
  var PACKAGE_FILENAME = 'package.json';
  var packageJSON = grunt.file.readJSON( PACKAGE_FILENAME );
  assert( packageJSON.name, 'name missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.version, 'version missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.license, 'license missing from ' + PACKAGE_FILENAME );

  // only repositories that are runnable have PhET-specific entries. The isJSOnly flag is used by dot/kite/scenery, so they need packageJSON.phet to exist.
  if ( packageJSON.phet && !packageJSON.phet.isJSOnly ) {

    // verify that required fields are present
    assert( packageJSON.phet.requirejsNamespace, 'phet.requirejsNamespace missing from ' + PACKAGE_FILENAME );
  }
  else {

    // default so that non-build tasks run for repositories that aren't runnable
    packageJSON.phet = packageJSON.phet || {};
  }

  // chipper/build.json (required)
  var buildJSON = grunt.file.readJSON( '../chipper/build.json' );

  // $HOME/.phet/build-local.json (optional)
  var buildLocalJSON;
  try {
    buildLocalJSON = grunt.file.readJSON( process.env.HOME + '/.phet/build-local.json' );
  }
  catch( error ) {
    buildLocalJSON = {};
  }

  //------------------------------------------------------------------------------------
  // Assemble the buildConfig
  var brand = getBrand( grunt, buildLocalJSON );
  var buildConfig = {

    // These fields have no dependencies on other entries in buildConfig.
    name: packageJSON.name,
    version: getVersionForBrand( brand, packageJSON.version ),
    license: packageJSON.license,
    requirejsNamespace: packageJSON.phet.requirejsNamespace,

    // @public (read-write, phetIO)
    brand: brand,

    // {boolean} [isJSOnly] - If true, the minified JS output is the main product, and we'll build it slightly differently, with the following options:
    //   (NOTE: This different build skips post-build steps, see https://github.com/phetsims/scenery/issues/593)
    isJSOnly: !!packageJSON.phet.isJSOnly,
    requiresLodash: packageJSON.phet.requiresLodash, // {boolean} [requiresLodash] - If true, the built JS will check for Lodash's presence on startup.
    requiresJQuery: packageJSON.phet.requiresJQuery, // {boolean} [requiresJQuery] - If true, the built JS will check for jQuery's presence on startup.
    assignGlobals: packageJSON.phet.assignGlobals, // {Object} assignGlobals - If isJSOnly is true, this should be an object map from window globals
                                                   // as {string}s to the require.js names as {string}s, e.g. ```window.key = require( 'value' );```
    finalizeJS: packageJSON.phet.finalizeJS // {string} [finalizeJS] - If provided, this JS string will be inserted at the end of the main IIFE.
  };

  // These fields depend on other entries in buildConfig.
  buildConfig.simTitleStringKey = buildConfig.requirejsNamespace + '/' + buildConfig.name + '.title'; // REPO/repo.name
  buildConfig.phetLibs = getPhetLibs( packageJSON, buildJSON, buildConfig.brand );
  buildConfig.preload = getPreload( packageJSON, buildJSON, buildConfig.brand );
  validatePreload( buildConfig.preload, buildConfig.phetLibs );

  // @public preload specific to PhET-iO
  buildConfig.phetioPreload = getPreloadForBrand( buildJSON, 'phet-io' );
  validatePreload( buildConfig.phetioPreload, getPhetLibs( packageJSON, buildJSON, 'phet-io' ) );

  buildConfig.licenseKeys = getLicenseKeys( packageJSON, buildJSON, buildConfig.brand, buildConfig.preload );
  buildConfig.locales = getLocales( grunt, buildConfig.name );
  buildConfig.availableLocales = [ ChipperConstants.FALLBACK_LOCALE ].concat( getLocalesFromRepository( buildConfig.name ) );

  grunt.log.debug( 'buildConfig=' + JSON.stringify( buildConfig, null, 2 ) );
  return buildConfig;
};
<commit_msg>remove log statements, https://github.com/phetsims/phet-io/issues/971<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Gets configuration information that is required by the 'build' task.
 * Reads information from multiple places, including:
 *
 * chipper/build.json
 * $HOME/.phet/build-local.json
 * REPO/package.json
 * grunt.options( optionName )
 *
 * All fields are @public (read-only).
 * Fields include:
 *
 * {string} name - name of the repository being built
 * {string} version - version identifier
 * {string} license - license
 * {string} simTitleStringKey - key of the sim's title string
 * {string} requirejsNamespace - the repository's requirejs namespace in config.js, eg AREA_BUILDER
 * {string} brand - brand identifier
 * {string[]} phetLibs - other repositories that are required by the sim
 * {string[]} preload - scripts that need to be preloaded in the .html file, in the order that they will be preloaded
 * {string[]} licenseKeys - keys to licenses in sherpa/lib/license.json, for third-party dependencies
 * {string[]} locales - locales to build
 * {string[]} availableLocales - locales for which translations are available
 * {Object} gruntConfig
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
/* eslint-env node */
'use strict';

// built-in node APIs
var assert = require( 'assert' );

// 3rd-party packages
var _ = require( '../../../sherpa/lib/lodash-2.4.1.min' ); // eslint-disable-line require-statement-match

// modules
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );
var getVersionForBrand = require( '../../../chipper/js/getVersionForBrand' );
var getBrand = require( '../../../chipper/js/grunt/getBrand' );

/**
 * @param {Object} grunt - the grunt instance
 */
module.exports = function( grunt ) {

  /**
   * Gets phetLibs, the set of repositories on which the repository being built depends.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPhetLibs( packageJSON, buildJSON, brand ) {

    // start with package.json
    var phetLibs = packageJSON.phet.phetLibs || [];

    // add the repo that's being built
    phetLibs.push( packageJSON.name );

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].phetLibs ) {
        phetLibs = phetLibs.concat( buildJSON[ id ].phetLibs );
      }
    } );

    // sort and remove duplicates
    return _.uniq( phetLibs.sort() );
  }

  /**
   * Gets preload, the set of scripts to be preloaded in the .html file.
   * NOTE! Order of the return value is significant, since it corresponds to the order in which scripts will be preloaded.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPreload( packageJSON, buildJSON, brand ) {

    var preload = [];

    // add preloads that are common to all sims, from build.json
    if ( buildJSON.common && buildJSON.common.preload ) {
      preload = preload.concat( buildJSON.common.preload );
    }

    // add sim-specific preloads from package.json
    if ( packageJSON.phet.preload ) {
      preload = preload.concat( packageJSON.phet.preload );
    }

    // add brand-specific preloads from build.json
    if ( buildJSON[ brand ] && buildJSON[ brand ].preload ) {
      preload = preload.concat( buildJSON[ brand ].preload );
    }

    // add brand-specific preloads from package.json
    if ( packageJSON.phet[ brand ] && packageJSON.phet[ brand ].preload ) {
      preload = preload.concat( packageJSON.phet[ brand ].preload );
    }

    // remove duplicates (do NOT sort, order is significant!)
    return _.uniq( preload );
  }

  /**
   * Used for retrieving brand-specific preload
   * @param buildJSON
   * @param brand
   * @returns {string[]} - empty array if there are no preloads
   */
  function getPreloadForBrand( buildJSON, brand ) {
    var preload = [];

    // add brand-specific preloads from build.json
    if ( buildJSON[ brand ] && buildJSON[ brand ].preload ) {
      preload = buildJSON[ brand ].preload;
    }

    if ( packageJSON.phet[ brand ] && packageJSON.phet[ brand ].preload ) {
      preload = preload.concat( packageJSON.phet[ brand ].preload );
    }
    return preload;
  }

  /**
   * Gets the license keys for sherpa (third-party) libs that are used.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @param {string[]} preload
   * @returns {string[]}
   */
  function getLicenseKeys( packageJSON, buildJSON, brand, preload ) {

    // start with package.json
    var licenseKeys = packageJSON.phet.licenseKeys || [];

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].licenseKeys ) {
        licenseKeys = licenseKeys.concat( buildJSON[ id ].licenseKeys );
      }
    } );

    // Extract keys from preload for sherpa (third-party) dependencies
    preload.forEach( function( path ) {
      if ( path.indexOf( '/sherpa/' ) !== -1 ) {
        var lastSlash = path.lastIndexOf( '/' );
        var key = path.substring( lastSlash + 1 );
        licenseKeys.push( key );
      }
    } );

    // sort and remove duplicates
    return _.uniq( _.sortBy( licenseKeys, function( key ) { return key.toUpperCase(); } ) );
  }

  /*
   * Gets the locales from a repository, by inspecting the names of the string files in babel for that repository.
   *
   * @param {string} repository - name of the repository to get locales from
   */
  function getLocalesFromRepository( repository ) {

    // confirm that the repository has a strings directory
    var stringsDirectory = '../babel/' + repository;
    assert( grunt.file.isDir(), stringsDirectory + 'is not a directory' );

    // Get names of string files.
    var stringFiles = grunt.file.expand( stringsDirectory + '/' + repository + '-strings_*.json' );

    // Don't fail out if there are no string files, as this is a normal condition when building new simulations
    if ( stringFiles.length === 0 ) {
      grunt.log.debug( 'No string files found in ' + stringsDirectory + ' for repository ' + repository );
      return [];
    }

    // Extract the locales from the file names.
    // File names must have a form like 'graphing-lines-strings_ar_SA.json', where no '_' appear in the repo name.
    var locales = stringFiles.map( function( filename ) {
      return filename.substring( filename.indexOf( '_' ) + 1, filename.lastIndexOf( '.' ) );
    } );
    assert( locales.length > 0, 'no locales found in ' + stringsDirectory );

    return locales;
  }

  /**
   * Gets the set of locales to be built.
   *
   * The grunt options are:
   *
   * --locales=* : all locales from the repo's strings/ directory
   * --locales=fr : French
   * --locales=ar,fr,es : Arabic, French and Spanish (comma-separated locales)
   * --localesRepo=beers-law-lab: all locales from another repository's strings/ directory, ignored if --locales is specified
   *
   * @param {Object} grunt - the grunt instance
   * @param {string} repository - name of the repository that is being built
   */
  function getLocales( grunt, repository ) {

    var locales = [ ChipperConstants.FALLBACK_LOCALE ];
    var localesValue = grunt.option( 'locales' );

    if ( localesValue ) {
      if ( localesValue === '*' ) {
        locales = locales.concat( getLocalesFromRepository( repository ) ); // all locales for the repository that we're building
      }
      else {
        // use only the specified locales, which may not include the fallback
        locales = localesValue.split( ',' );
      }
    }
    else {
      var localesRepo = grunt.option( 'localesRepo' );
      if ( localesRepo ) {
        locales = locales.concat( getLocalesFromRepository( localesRepo ) ); // all locales for some other repository
      }
    }

    return _.uniq( locales.sort() );
  }

  /**
   * Verifies that preload repositories are included in phetLib.
   *
   * @param {string[]} preload
   * @param {string[]} phetLibs
   */
  function validatePreload( preload, phetLibs ) {
    var missingRepositories = [];
    preload.forEach( function( entry ) {

      // preload entries should start with '..', e.g. "../assert/js/assert.js"
      assert( entry.split( '/' )[ 0 ] === '..', 'malformed preload entry: ' + entry );

      // the preload's repository should be in phetLib
      var repositoryName = entry.split( '/' )[ 1 ];
      if ( phetLibs.indexOf( repositoryName ) === -1 && missingRepositories.indexOf( repositoryName ) === -1 ) {
        missingRepositories.push( repositoryName );
      }
    } );
    assert( missingRepositories.length === 0,
      'phetLib is missing repositories required by preload: ' + missingRepositories.toString() );
  }

  //------------------------------------------------------------------------------------
  // read configuration files

  // ./package.json (required)
  var PACKAGE_FILENAME = 'package.json';
  var packageJSON = grunt.file.readJSON( PACKAGE_FILENAME );
  assert( packageJSON.name, 'name missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.version, 'version missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.license, 'license missing from ' + PACKAGE_FILENAME );

  // only repositories that are runnable have PhET-specific entries. The isJSOnly flag is used by dot/kite/scenery, so they need packageJSON.phet to exist.
  if ( packageJSON.phet && !packageJSON.phet.isJSOnly ) {

    // verify that required fields are present
    assert( packageJSON.phet.requirejsNamespace, 'phet.requirejsNamespace missing from ' + PACKAGE_FILENAME );
  }
  else {

    // default so that non-build tasks run for repositories that aren't runnable
    packageJSON.phet = packageJSON.phet || {};
  }

  // chipper/build.json (required)
  var buildJSON = grunt.file.readJSON( '../chipper/build.json' );

  // $HOME/.phet/build-local.json (optional)
  var buildLocalJSON;
  try {
    buildLocalJSON = grunt.file.readJSON( process.env.HOME + '/.phet/build-local.json' );
  }
  catch( error ) {
    buildLocalJSON = {};
  }

  //------------------------------------------------------------------------------------
  // Assemble the buildConfig
  var brand = getBrand( grunt, buildLocalJSON );
  var buildConfig = {

    // These fields have no dependencies on other entries in buildConfig.
    name: packageJSON.name,
    version: getVersionForBrand( brand, packageJSON.version ),
    license: packageJSON.license,
    requirejsNamespace: packageJSON.phet.requirejsNamespace,

    // @public (read-write, phetIO)
    brand: brand,

    // {boolean} [isJSOnly] - If true, the minified JS output is the main product, and we'll build it slightly differently, with the following options:
    //   (NOTE: This different build skips post-build steps, see https://github.com/phetsims/scenery/issues/593)
    isJSOnly: !!packageJSON.phet.isJSOnly,
    requiresLodash: packageJSON.phet.requiresLodash, // {boolean} [requiresLodash] - If true, the built JS will check for Lodash's presence on startup.
    requiresJQuery: packageJSON.phet.requiresJQuery, // {boolean} [requiresJQuery] - If true, the built JS will check for jQuery's presence on startup.
    assignGlobals: packageJSON.phet.assignGlobals, // {Object} assignGlobals - If isJSOnly is true, this should be an object map from window globals
                                                   // as {string}s to the require.js names as {string}s, e.g. ```window.key = require( 'value' );```
    finalizeJS: packageJSON.phet.finalizeJS // {string} [finalizeJS] - If provided, this JS string will be inserted at the end of the main IIFE.
  };

  // These fields depend on other entries in buildConfig.
  buildConfig.simTitleStringKey = buildConfig.requirejsNamespace + '/' + buildConfig.name + '.title'; // REPO/repo.name
  buildConfig.phetLibs = getPhetLibs( packageJSON, buildJSON, buildConfig.brand );
  buildConfig.preload = getPreload( packageJSON, buildJSON, buildConfig.brand );
  validatePreload( buildConfig.preload, buildConfig.phetLibs );

  // @public preload specific to PhET-iO
  buildConfig.phetioPreload = getPreloadForBrand( buildJSON, 'phet-io' );
  validatePreload( buildConfig.phetioPreload, getPhetLibs( packageJSON, buildJSON, 'phet-io' ) );

  buildConfig.licenseKeys = getLicenseKeys( packageJSON, buildJSON, buildConfig.brand, buildConfig.preload );
  buildConfig.locales = getLocales( grunt, buildConfig.name );
  buildConfig.availableLocales = [ ChipperConstants.FALLBACK_LOCALE ].concat( getLocalesFromRepository( buildConfig.name ) );

  grunt.log.debug( 'buildConfig=' + JSON.stringify( buildConfig, null, 2 ) );
  return buildConfig;
};
<|endoftext|>


<commit_before>angular.module('myApp').controller('FilterModalCtrl', function($scope, $modalInstance, $timeout, Restangular, params, TableFilter, Modal) {
    'use strict';

    $scope.selected = params.selected ? params.selected : [];
    var queryParams = params.queryParams ? params.queryParams : {fields: 'color'};
    $scope.expandedNodes = [];

    var expandFirstLevel = function() {
        _.forEach($scope.tree, function(filter) {
            $scope.expandedNodes.push(filter);
        });
    };

    if ($scope.tree) {
        expandFirstLevel();
    } else {
        $scope.$watch('tree', function() {
            expandFirstLevel();
        });
    }

    $scope.treeOptions = {
        dirSelectable: true,
        injectClasses: {
            li: "text-2em",
            iExpanded: "fa fa-minus text-2em fa-fw",
            iCollapsed: "fa fa-plus text-2em fa-fw",
            iLeaf: "fa fa-fw"
        },

        // necessary because some filter are in multiple places and there are conflicts without strict equality
        equality: function(node1, node2) {
            return node1 === node2;
        }
    };

    var searchAndOpenFoldersWithSelectedElements = function(elements) {
        var isSelected = false;
        angular.forEach(elements, function(node) {
            if (searchAndOpenFoldersWithSelectedElements(node.children)) {
                isSelected = true;
                $scope.expandedNodes.push(node);
            }

            if (node.selected) {
                isSelected = true;
            }
        });

        return isSelected;
    };

    var initSelected = function(elements) {
        _.forEach(elements, function(node) {

            // if node is selected, mark or unmark it
            var found = _.find($scope.selected, function(el) {
                return node.id == el.id;
            });

            if (found) {
                addToSelected(node);
            } else {
                removeFromSelected(node);
            }

            // repeat operation for children
            initSelected(node.children);
        });
    };

    var tagAllSimilarFilters = function(filter, filters) {
        _.forEach(filters, function(f) {
            if (f.id == filter.id) {
                f.selected = filter.selected;
            } else {
                tagAllSimilarFilters(filter, f.children);
            }
        });
    };

    var addToSelected = function(filter) {

        if (!params.multiple) {
            _.forEach($scope.selected, function(el) {
                removeFromSelected(el);
            });
        }

        filter.selected = true;
        var found = _.find($scope.selected, function(s) {
            return s.id == filter.id;
        });

        if (!found) {
            $scope.selected.push(filter);
        }

        tagAllSimilarFilters(filter, $scope.tree);
    };

    var removeFromSelected = function(filter) {
        filter.selected = false;
        _.remove($scope.selected, function(f) {
            return f.id == filter.id;
        });
        tagAllSimilarFilters(filter, $scope.tree);
    };

    TableFilter.getTree(queryParams).then(function(tree) {
        $scope.tree = tree;
        initSelected($scope.tree);
        searchAndOpenFoldersWithSelectedElements($scope.tree);
    });

    var lastSelected = null;
    $scope.showSelected = function(selection) {

        if (selection) {
            lastSelected = selection;
        } else {
            selection = lastSelected;
        }

        if (selection) {
            if (!selection.selected) {
                addToSelected(selection);
            } else {
                removeFromSelected(selection);
            }

            $scope.selectedNode = selection;
        }
    };

    $scope.selectFilters = function() {
        if (!params.multiple) {
            $scope.selected = $scope.selected[0];
        }
        $modalInstance.close($scope.selected);
    };

    $scope.remove = function(filter) {
        Modal.confirmDelete(filter, {returnUrl: '/admin/filter'}).then(function() {
            TableFilter.getTree(queryParams, true).then(function(tree) {
                $scope.tree = tree;
            });
        });

    };

    $scope.$dismiss = function() {
        $modalInstance.dismiss();
    };

});
<commit_msg>Deploy first level of filter hierarchy #2909<commit_after>angular.module('myApp').controller('FilterModalCtrl', function($scope, $modalInstance, $timeout, Restangular, params, TableFilter, Modal) {
    'use strict';

    $scope.selected = params.selected ? params.selected : [];
    var queryParams = params.queryParams ? params.queryParams : {fields: 'color'};
    $scope.expandedNodes = [];

    $scope.$watch('tree', function() {
        _.forEach($scope.tree, function(filter) {
            $scope.expandedNodes.push(filter);
        });
    });

    $scope.treeOptions = {
        dirSelectable: true,
        injectClasses: {
            li: "text-2em",
            iExpanded: "fa fa-minus text-2em fa-fw",
            iCollapsed: "fa fa-plus text-2em fa-fw",
            iLeaf: "fa fa-fw"
        },

        // necessary because some filter are in multiple places and there are conflicts without strict equality
        equality: function(node1, node2) {
            return node1 === node2;
        }
    };

    var searchAndOpenFoldersWithSelectedElements = function(elements) {
        var isSelected = false;
        angular.forEach(elements, function(node) {
            if (searchAndOpenFoldersWithSelectedElements(node.children)) {
                isSelected = true;
                $scope.expandedNodes.push(node);
            }

            if (node.selected) {
                isSelected = true;
            }
        });

        return isSelected;
    };

    var initSelected = function(elements) {
        _.forEach(elements, function(node) {

            // if node is selected, mark or unmark it
            var found = _.find($scope.selected, function(el) {
                return node.id == el.id;
            });

            if (found) {
                addToSelected(node);
            } else {
                removeFromSelected(node);
            }

            // repeat operation for children
            initSelected(node.children);
        });
    };

    var tagAllSimilarFilters = function(filter, filters) {
        _.forEach(filters, function(f) {
            if (f.id == filter.id) {
                f.selected = filter.selected;
            } else {
                tagAllSimilarFilters(filter, f.children);
            }
        });
    };

    var addToSelected = function(filter) {

        if (!params.multiple) {
            _.forEach($scope.selected, function(el) {
                removeFromSelected(el);
            });
        }

        filter.selected = true;
        var found = _.find($scope.selected, function(s) {
            return s.id == filter.id;
        });

        if (!found) {
            $scope.selected.push(filter);
        }

        tagAllSimilarFilters(filter, $scope.tree);
    };

    var removeFromSelected = function(filter) {
        filter.selected = false;
        _.remove($scope.selected, function(f) {
            return f.id == filter.id;
        });
        tagAllSimilarFilters(filter, $scope.tree);
    };

    TableFilter.getTree(queryParams).then(function(tree) {
        $scope.tree = tree;
        initSelected($scope.tree);
        searchAndOpenFoldersWithSelectedElements($scope.tree);
    });

    var lastSelected = null;
    $scope.showSelected = function(selection) {

        if (selection) {
            lastSelected = selection;
        } else {
            selection = lastSelected;
        }

        if (selection) {
            if (!selection.selected) {
                addToSelected(selection);
            } else {
                removeFromSelected(selection);
            }

            $scope.selectedNode = selection;
        }
    };

    $scope.selectFilters = function() {
        if (!params.multiple) {
            $scope.selected = $scope.selected[0];
        }
        $modalInstance.close($scope.selected);
    };

    $scope.remove = function(filter) {
        Modal.confirmDelete(filter, {returnUrl: '/admin/filter'}).then(function() {
            TableFilter.getTree(queryParams, true).then(function(tree) {
                $scope.tree = tree;
            });
        });

    };

    $scope.$dismiss = function() {
        $modalInstance.dismiss();
    };

});
<|endoftext|>