<commit_before>function CellInfoCollection() {
}

CellInfoCollection.prototype.getInfo = function (cellInfoArr, row, col) {
  for (var i = 0, ilen = cellInfoArr.length; i < ilen; i++) {
    if (cellInfoArr[i].row <= row && cellInfoArr[i].row + cellInfoArr[i].rowspan - 1 >= row && cellInfoArr[i].col <= col && cellInfoArr[i].col + cellInfoArr[i].colspan - 1 >= col) {
      return cellInfoArr[i];
    }
  }
};

CellInfoCollection.prototype.setInfo = function (cellInfoArr, info) {
  for (var i = 0, ilen = cellInfoArr.length; i < ilen; i++) {
    if (cellInfoArr[i].row === info.row && cellInfoArr[i].col === info.col) {
      cellInfoArr[i] = info;
      return;
    }
  }
  cellInfoArr.push(info);
};

CellInfoCollection.prototype.removeInfo = function (cellInfoArr, row, col) {
  for (var i = 0, ilen = cellInfoArr.length; i < ilen; i++) {
    if (cellInfoArr[i].row === row && cellInfoArr[i].col === col) {
      cellInfoArr.splice(i, 1);
      break;
    }
  }
};

/**
 * Plugin used to merge cells in Handsontable
 * @constructor
 */
function MergeCells(instance) {
  this.instance = instance;
  this.mergedCellInfoCollection = new CellInfoCollection();
}

/**
 * @param cellRange (WalkontableCellRange)
 */
MergeCells.prototype.canMergeRange = function (cellRange) {
  //is more than one cell selected
  if (cellRange.isSingle()) {
    return false;
  }

  //is it a valid cell range
  if (!cellRange.isValid(this.instance.view.wt)) {
    return false;
  }

  return true;
};

MergeCells.prototype.mergeRange = function (cellRange) {
  if (!this.canMergeRange(cellRange)) {
    return;
  }

  //normalize top left corner
  var topLeft = cellRange.getTopLeftCorner();
  var bottomRight = cellRange.getBottomRightCorner();

  var mergeParent = {};
  mergeParent.row = topLeft.row;
  mergeParent.col = topLeft.col;
  mergeParent.rowspan = bottomRight.row - topLeft.row + 1; //TD has rowspan == 1 by default. rowspan == 2 means spread over 2 cells
  mergeParent.colspan = bottomRight.col - topLeft.col + 1;
  this.mergedCellInfoCollection.setInfo(this.instance.getSettings().mergeCells, mergeParent);
};

MergeCells.prototype.mergeOrUnmergeSelection = function () {
  var sel = this.instance.getSelected();
  var info = this.mergedCellInfoCollection.getInfo(this.instance.getSettings().mergeCells, sel[0], sel[1]);
  if (info) {
    //unmerge
    this.unmergeSelection();
  }
  else {
    //merge
    this.mergeSelection();
  }
};

MergeCells.prototype.mergeSelection = function () {
  var sel = this.instance.getSelected();
  var cellRange = new WalkontableCellRange(new WalkontableCellCoords(sel[0], sel[1]), new WalkontableCellCoords(sel[2], sel[3]));
  this.mergeRange(cellRange);
  this.instance.render();
};

MergeCells.prototype.unmergeSelection = function () {
  var sel = this.instance.getSelected();
  var info = this.mergedCellInfoCollection.getInfo(this.instance.getSettings().mergeCells, sel[0], sel[1]);
  this.mergedCellInfoCollection.removeInfo(this.instance.getSettings().mergeCells, info.row, info.col);
  this.instance.render();
};

MergeCells.prototype.applySpanProperties = function (TD, row, col) {
  var info = this.mergedCellInfoCollection.getInfo(this.instance.getSettings().mergeCells, row, col);
  if (info) {
    if (info.row === row && info.col === col) {
      TD.setAttribute('rowspan', info.rowspan);
      TD.setAttribute('colspan', info.colspan);
    }
    else {
      TD.style.display = "none";
    }
  }
  else {
    TD.removeAttribute('rowspan');
    TD.removeAttribute('colspan');
  }
};

if (typeof Handsontable !== 'undefined') {
  var init = function () {
    var instance = this;
    var mergeCellsSetting = instance.getSettings().mergeCells;

    //mergeCellsSetting = true;

    if (mergeCellsSetting) {
      if (!instance.mergeCells) {
        instance.mergeCells = new MergeCells(instance);
      }
    }
  };

  Handsontable.PluginHooks.add('beforeInit', init);

  var onBeforeKeyDown = function (event) {
    if (!this.mergeCells) {
      return;
    }

    var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;

    if (ctrlDown) {
      if (event.keyCode === 77) { //CTRL + M
        this.mergeCells.mergeOrUnmergeSelection();
        event.stopImmediatePropagation();
      }
    }
  };

  Handsontable.PluginHooks.add('beforeKeyDown', onBeforeKeyDown);

  Handsontable.PluginHooks.add('afterContextMenuDefaultOptions', function (defaultOptions) {
    if (!this.getSettings().mergeCells) {
      return;
    }

    defaultOptions.items.mergeCellsSeparator = Handsontable.ContextMenu.SEPARATOR;

    defaultOptions.items.mergeCells = {
      name: function () {
        var sel = this.getSelected();
        var info = this.mergeCells.mergedCellInfoCollection.getInfo(this.getSettings().mergeCells, sel[0], sel[1]);
        if (info) {
          return 'Unmerge cells';
        }
        else {
          return 'Merge cells';
        }
      },
      callback: function () {
        this.mergeCells.mergeOrUnmergeSelection();
      },
      disabled: function () {
        return false;
      }
    };
  });

  Handsontable.PluginHooks.add('afterRenderer', function (TD, row, col, prop, value, cellProperties) {
    if (this.mergeCells) {
      this.mergeCells.applySpanProperties(TD, row, col);
    }
  });

  var beforeInit = function () {
    var mergeCells = this.getSettings().mergeCells;
    if (mergeCells) {
      if (mergeCells === true) {
        this.getSettings().mergeCells = [];
      }
    }
  };

  var modifyTransformFactory = function (hook) {
    return function (delta) {
      var mergeCellsSetting = this.getSettings().mergeCells;
      if (mergeCellsSetting) {
        var selRange = this.getSelectedRange();
        var current;
        switch (hook) {
          case 'modifyTransformStartRow':
          case 'modifyTransformStartCol':
            current = selRange.from;
            break;

          case 'modifyTransformEndRow':
          case 'modifyTransformEndCol':
            current = selRange.to;
            break;
        }
        var mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(mergeCellsSetting, current.row, current.col);
        if (mergeParent) {
          switch (hook) {
            case 'modifyTransformStartRow':
            case 'modifyTransformEndRow':
              if (delta > 0) {
                return mergeParent.row - current.row + mergeParent.rowspan - 1 + delta;
              }
              else if (delta < 0) {
                return mergeParent.row - current.row + delta;
              }
              break;

            case 'modifyTransformStartCol':
            case 'modifyTransformEndCol':
              if (delta > 0) {
                return mergeParent.col - current.col + mergeParent.colspan - 1 + delta;
              }
              else if (delta < 0) {
                return mergeParent.col - current.col + delta;
              }
              break;
          }
        }
      }
      return delta;
    }
  };

  var afterSelection = function (fromRow, fromCol, toRow, toCol) {
    var mergeCellsSetting = this.getSettings().mergeCells;
    if (mergeCellsSetting) {
      var selRange = this.getSelectedRange();
      var fromInfo = this.mergeCells.mergedCellInfoCollection.getInfo(mergeCellsSetting, selRange.from.row, selRange.from.col);
      if (fromInfo) {
        var newFromCellCoords = new WalkontableCellCoords(fromInfo.row, fromInfo.col);
        this.view.wt.selections.current.replace(selRange.from, newFromCellCoords);
        this.view.wt.selections.area.replace(selRange.from, newFromCellCoords);
        this.view.wt.selections.highlight.replace(selRange.from, newFromCellCoords);
      }
      var toInfo = this.mergeCells.mergedCellInfoCollection.getInfo(mergeCellsSetting, selRange.to.row, selRange.to.col);
      if (toInfo) {
        var newToCellCoords = new WalkontableCellCoords(toInfo.row, toInfo.col);
        this.view.wt.selections.current.replace(selRange.to, newToCellCoords);
        this.view.wt.selections.area.replace(selRange.to, newToCellCoords);
        this.view.wt.selections.highlight.replace(selRange.to, newToCellCoords);
      }
    }
  };

  Handsontable.PluginHooks.add('beforeInit', beforeInit);
  Handsontable.PluginHooks.add('modifyTransformStartRow', modifyTransformFactory('modifyTransformStartRow'));
  Handsontable.PluginHooks.add('modifyTransformStartCol', modifyTransformFactory('modifyTransformStartCol'));
  Handsontable.PluginHooks.add('modifyTransformEndRow', modifyTransformFactory('modifyTransformEndRow'));
  Handsontable.PluginHooks.add('modifyTransformEndCol', modifyTransformFactory('modifyTransformEndCol'));
  Handsontable.PluginHooks.add('afterSelection', afterSelection);

  Handsontable.MergeCells = MergeCells;
}
<commit_msg>Changed CellInfoCollection to an actual collection (it now extends an array object). Removed beforeInit listener and cleaned up code.<commit_after>function CellInfoCollection(initialCollection) {

  var collection = [];

  collection.getInfo = function (row, col) {
    for (var i = 0, ilen = this.length; i < ilen; i++) {
      if (this[i].row <= row && this[i].row + this[i].rowspan - 1 >= row && this[i].col <= col && this[i].col + this[i].colspan - 1 >= col) {
        return this[i];
      }
    }
  };

  collection.setInfo = function (info) {
    for (var i = 0, ilen = this.length; i < ilen; i++) {
      if (this[i].row === info.row && this[i].col === info.col) {
        this[i] = info;
        return;
      }
    }
    this.push(info);
  };

  collection.removeInfo = function (row, col) {
    for (var i = 0, ilen = this.length; i < ilen; i++) {
      if (this[i].row === row && this[i].col === col) {
        this.splice(i, 1);
        break;
      }
    }
  };

  if (Handsontable.helper.isArray(initialCollection)){
    initialCollection.forEach(function (mergeCellInfo){
      collection.setInfo(mergeCellInfo);
    });
  }

  return collection;

}

/**
 * Plugin used to merge cells in Handsontable
 * @constructor
 */
function MergeCells(instance) {
  this.instance = instance;
  this.mergedCellInfoCollection = new CellInfoCollection(instance.getSettings().mergeCells);
}

/**
 * @param cellRange (WalkontableCellRange)
 */
MergeCells.prototype.canMergeRange = function (cellRange) {
  //is more than one cell selected
  if (cellRange.isSingle()) {
    return false;
  }

  //is it a valid cell range
  if (!cellRange.isValid(this.instance.view.wt)) {
    return false;
  }

  return true;
};

MergeCells.prototype.mergeRange = function (cellRange) {
  if (!this.canMergeRange(cellRange)) {
    return;
  }

  //normalize top left corner
  var topLeft = cellRange.getTopLeftCorner();
  var bottomRight = cellRange.getBottomRightCorner();

  var mergeParent = {};
  mergeParent.row = topLeft.row;
  mergeParent.col = topLeft.col;
  mergeParent.rowspan = bottomRight.row - topLeft.row + 1; //TD has rowspan == 1 by default. rowspan == 2 means spread over 2 cells
  mergeParent.colspan = bottomRight.col - topLeft.col + 1;
  this.mergedCellInfoCollection.setInfo(mergeParent);
};

MergeCells.prototype.mergeOrUnmergeSelection = function () {
  var sel = this.instance.getSelected();
  var info = this.mergedCellInfoCollection.getInfo(sel[0], sel[1]);
  if (info) {
    //unmerge
    this.unmergeSelection();
  }
  else {
    //merge
    this.mergeSelection();
  }
};

MergeCells.prototype.mergeSelection = function () {
  var sel = this.instance.getSelected();
  var cellRange = new WalkontableCellRange(new WalkontableCellCoords(sel[0], sel[1]), new WalkontableCellCoords(sel[2], sel[3]));
  this.mergeRange(cellRange);
  this.instance.render();
};

MergeCells.prototype.unmergeSelection = function () {
  var sel = this.instance.getSelected();
  var info = this.mergedCellInfoCollection.getInfo(sel[0], sel[1]);
  this.mergedCellInfoCollection.removeInfo(info.row, info.col);
  this.instance.render();
};

MergeCells.prototype.applySpanProperties = function (TD, row, col) {
  var info = this.mergedCellInfoCollection.getInfo(row, col);
  if (info) {
    if (info.row === row && info.col === col) {
      TD.setAttribute('rowspan', info.rowspan);
      TD.setAttribute('colspan', info.colspan);
    }
    else {
      TD.style.display = "none";
    }
  }
  else {
    TD.removeAttribute('rowspan');
    TD.removeAttribute('colspan');
  }
};

if (typeof Handsontable == 'undefined') {
  throw new Error('Handsontable is not defined');
}

var init = function () {
  var instance = this;
  var mergeCellsSetting = instance.getSettings().mergeCells;

  if (mergeCellsSetting) {
    if (!instance.mergeCells) {
      instance.mergeCells = new MergeCells(instance);
    }
  }
};

var onBeforeKeyDown = function (event) {
  if (!this.mergeCells) {
    return;
  }

  var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;

  if (ctrlDown) {
    if (event.keyCode === 77) { //CTRL + M
      this.mergeCells.mergeOrUnmergeSelection();
      event.stopImmediatePropagation();
    }
  }
};

var addMergeActionsToContextMenu = function (defaultOptions) {
  if (!this.getSettings().mergeCells) {
    return;
  }

  defaultOptions.items.mergeCellsSeparator = Handsontable.ContextMenu.SEPARATOR;

  defaultOptions.items.mergeCells = {
    name: function () {
      var sel = this.getSelected();
      var info = this.mergeCells.mergedCellInfoCollection.getInfo(this.getSettings().mergeCells, sel[0], sel[1]);
      if (info) {
        return 'Unmerge cells';
      }
      else {
        return 'Merge cells';
      }
    },
    callback: function () {
      this.mergeCells.mergeOrUnmergeSelection();
    },
    disabled: function () {
      return false;
    }
  };
};

var afterRenderer = function (TD, row, col, prop, value, cellProperties) {
  if (this.mergeCells) {
    this.mergeCells.applySpanProperties(TD, row, col);
  }
};

var modifyTransformFactory = function (hook) {
  return function (delta) {
    var mergeCellsSetting = this.getSettings().mergeCells;
    if (mergeCellsSetting) {
      var selRange = this.getSelectedRange();
      var current;
      switch (hook) {
        case 'modifyTransformStartRow':
        case 'modifyTransformStartCol':
          current = selRange.from;
          break;

        case 'modifyTransformEndRow':
        case 'modifyTransformEndCol':
          current = selRange.to;
          break;
      }
      var mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(mergeCellsSetting, current.row, current.col);
      if (mergeParent) {
        switch (hook) {
          case 'modifyTransformStartRow':
          case 'modifyTransformEndRow':
            if (delta > 0) {
              return mergeParent.row - current.row + mergeParent.rowspan - 1 + delta;
            }
            else if (delta < 0) {
              return mergeParent.row - current.row + delta;
            }
            break;

          case 'modifyTransformStartCol':
          case 'modifyTransformEndCol':
            if (delta > 0) {
              return mergeParent.col - current.col + mergeParent.colspan - 1 + delta;
            }
            else if (delta < 0) {
              return mergeParent.col - current.col + delta;
            }
            break;
        }
      }
    }
    return delta;
  }
};

var afterSelection = function (fromRow, fromCol, toRow, toCol) {
  var mergeCellsSetting = this.getSettings().mergeCells;
  if (mergeCellsSetting) {
    var selRange = this.getSelectedRange();
    var fromInfo = this.mergeCells.mergedCellInfoCollection.getInfo(mergeCellsSetting, selRange.from.row, selRange.from.col);
    if (fromInfo) {
      var newFromCellCoords = new WalkontableCellCoords(fromInfo.row, fromInfo.col);
      this.view.wt.selections.current.replace(selRange.from, newFromCellCoords);
      this.view.wt.selections.area.replace(selRange.from, newFromCellCoords);
      this.view.wt.selections.highlight.replace(selRange.from, newFromCellCoords);
    }
    var toInfo = this.mergeCells.mergedCellInfoCollection.getInfo(mergeCellsSetting, selRange.to.row, selRange.to.col);
    if (toInfo) {
      var newToCellCoords = new WalkontableCellCoords(toInfo.row, toInfo.col);
      this.view.wt.selections.current.replace(selRange.to, newToCellCoords);
      this.view.wt.selections.area.replace(selRange.to, newToCellCoords);
      this.view.wt.selections.highlight.replace(selRange.to, newToCellCoords);
    }
  }
};

Handsontable.PluginHooks.add('beforeInit', init);
Handsontable.PluginHooks.add('beforeKeyDown', onBeforeKeyDown);
Handsontable.PluginHooks.add('modifyTransformStartRow', modifyTransformFactory('modifyTransformStartRow'));
Handsontable.PluginHooks.add('modifyTransformStartCol', modifyTransformFactory('modifyTransformStartCol'));
Handsontable.PluginHooks.add('modifyTransformEndRow', modifyTransformFactory('modifyTransformEndRow'));
Handsontable.PluginHooks.add('modifyTransformEndCol', modifyTransformFactory('modifyTransformEndCol'));
Handsontable.PluginHooks.add('afterSelection', afterSelection);
Handsontable.PluginHooks.add('afterRenderer', afterRenderer);
Handsontable.PluginHooks.add('afterContextMenuDefaultOptions', addMergeActionsToContextMenu);

Handsontable.MergeCells = MergeCells;

<|endoftext|>


<commit_before>
var express = require('../');

describe('app', function(){
  describe('.render(name, fn)', function(){
    it('should support absolute paths', function(done){
      var app = express();

      app.locals.user = { name: 'tobi' };

      app.render(__dirname + '/fixtures/user.jade', function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should expose app.locals', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.locals.user = { name: 'tobi' };

      app.render('user.jade', function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should support index.<engine>', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.set('view engine', 'jade');

      app.render('blog/post', function(err, str){
        if (err) return done(err);
        str.should.equal('
blog post
');
        done();
      })
    })

    describe('when an error occurs', function(){
      it('should invoke the callback', function(done){
        var app = express();

        app.set('views', __dirname + '/fixtures');

        app.render('user.jade', function(err, str){
          // nextTick to prevent cyclic
          process.nextTick(function(){
            err.message.should.match(/user is not defined/);
            done();
          });
        })
      })
    })

    describe('when an extension is given', function(){
      it('should render the template', function(done){
        var app = express();

        app.set('views', __dirname + '/fixtures');

        app.render('email.jade', function(err, str){
          if (err) return done(err);
          str.should.equal('
This is an email
');
          done();
        })
      })
    })

    describe('when "view engine" is given', function(){
      it('should render the template', function(done){
        var app = express();

        app.set('view engine', 'jade');
        app.set('views', __dirname + '/fixtures');

        app.render('email', function(err, str){
          if (err) return done(err);
          str.should.equal('
This is an email
');
          done();
        })
      })
    })
  })

  describe('.render(name, options, fn)', function(){
    it('should render the template', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');

      var user = { name: 'tobi' };

      app.render('user.jade', { user: user }, function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should expose app.locals', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.locals.user = { name: 'tobi' };

      app.render('user.jade', {}, function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should give precedence to app.render() locals', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.locals.user = { name: 'tobi' };
      var jane = { name: 'jane' };

      app.render('user.jade', { user: jane }, function(err, str){
        if (err) return done(err);
        str.should.equal('
jane
');
        done();
      })
    })
  })
})
<commit_msg>more tests<commit_after>
var express = require('../');

describe('app', function(){
  describe('.render(name, fn)', function(){
    it('should support absolute paths', function(done){
      var app = express();

      app.locals.user = { name: 'tobi' };

      app.render(__dirname + '/fixtures/user.jade', function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should support absolute paths with "view engine"', function(done){
      var app = express();

      app.set('view engine', 'jade');
      app.locals.user = { name: 'tobi' };

      app.render(__dirname + '/fixtures/user', function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should expose app.locals', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.locals.user = { name: 'tobi' };

      app.render('user.jade', function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should support index.<engine>', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.set('view engine', 'jade');

      app.render('blog/post', function(err, str){
        if (err) return done(err);
        str.should.equal('
blog post
');
        done();
      })
    })

    describe('when an error occurs', function(){
      it('should invoke the callback', function(done){
        var app = express();

        app.set('views', __dirname + '/fixtures');

        app.render('user.jade', function(err, str){
          // nextTick to prevent cyclic
          process.nextTick(function(){
            err.message.should.match(/user is not defined/);
            done();
          });
        })
      })
    })

    describe('when an extension is given', function(){
      it('should render the template', function(done){
        var app = express();

        app.set('views', __dirname + '/fixtures');

        app.render('email.jade', function(err, str){
          if (err) return done(err);
          str.should.equal('
This is an email
');
          done();
        })
      })
    })

    describe('when "view engine" is given', function(){
      it('should render the template', function(done){
        var app = express();

        app.set('view engine', 'jade');
        app.set('views', __dirname + '/fixtures');

        app.render('email', function(err, str){
          if (err) return done(err);
          str.should.equal('
This is an email
');
          done();
        })
      })
    })
  })

  describe('.render(name, options, fn)', function(){
    it('should render the template', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');

      var user = { name: 'tobi' };

      app.render('user.jade', { user: user }, function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should expose app.locals', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.locals.user = { name: 'tobi' };

      app.render('user.jade', {}, function(err, str){
        if (err) return done(err);
        str.should.equal('
tobi
');
        done();
      })
    })

    it('should give precedence to app.render() locals', function(done){
      var app = express();

      app.set('views', __dirname + '/fixtures');
      app.locals.user = { name: 'tobi' };
      var jane = { name: 'jane' };

      app.render('user.jade', { user: jane }, function(err, str){
        if (err) return done(err);
        str.should.equal('
jane
');
        done();
      })
    })
  })
})
<|endoftext|>


<commit_before>module.exports = function (sails) {

	/**
	 * Module dependencies.
	 */

	var util		= require('../../../util');

	/**
	 * @param {Natural|String} id
	 * @param {String} controllerId
	 * @param {String} actionId
	 * @returns id ::
	 *		If `id` is undefined, no `id` was provided
	 *		If `id` is false, `id` is invalid, and probably unintentional
	 *		Otherwise, `id` is valid and probably intentional
	 */
	return function validId (id, controllerId, actionId) {

		// If expectIntegerId check is disabled, `id` is always ok
		if ( !sails.config.controllers.blueprints.expectIntegerId ) {
			return id;
		}

		// Ensure that id is numeric (unless this check is disabled)
		var castId = +id;
		if (id && util.isNaN(castId)) {

			// The other CRUD methods are special reserved words-- in which case we always pass
			// As long as the CRUD 'shortcut' blueprints are enabled, you cannot search for models
			// with an id of 'update', 'create', or 'destroy'
			var routeConf = sails.config.controllers.blueprints;
			if (	sails.config.controllers.blueprints.shortcuts && (
					id === 'update' ||
					id === 'create' ||
					id === 'destroy' )) {
				return false;
			}

			// If it's not, move on to next middleware
			// but emit a console warning explaining the situation
			// (if the app is in development mode):
			if (sails.config.environment === 'development') {
				sails.log.warn('\n',
							'Just then, you were prevented from being routed \n',
							'to the `' + actionId + '` blueprint for controller: ' + controllerId +
							' using `id='+id+'`.\n',
							'This is because REST blueprint routes expect natural number ids by default, '+
							'and so the `' + actionId + '()` middleware was skipped- \n',
							'If you\'d like to disable this restriction, you can do so by setting \n',
							'sails.config.controllers.blueprints.expectIntegerId = false');
			}
			return false;
		}

		// Id is an integer
		return id;
	};
};

<commit_msg>don't allow an ID of `find` in the shortcut blueprints<commit_after>module.exports = function (sails) {

	/**
	 * Module dependencies.
	 */

	var util		= require('../../../util');

	/**
	 * @param {Natural|String} id
	 * @param {String} controllerId
	 * @param {String} actionId
	 * @returns id ::
	 *		If `id` is undefined, no `id` was provided
	 *		If `id` is false, `id` is invalid, and probably unintentional
	 *		Otherwise, `id` is valid and probably intentional
	 */
	return function validId (id, controllerId, actionId) {

		// The other CRUD methods are special reserved words-- in which case we always pass
		// As long as the CRUD 'shortcut' blueprints are enabled, you cannot search for models
		// with an id of 'find', 'update', 'create', or 'destroy'
		var routeConf = sails.config.controllers.blueprints;
		if (	sails.config.controllers.blueprints.shortcuts && (
				id === 'find'   ||
				id === 'update' ||
				id === 'create' ||
				id === 'destroy' )) {
			return false;
		}

		// If expectIntegerId check is disabled, `id` is always ok
		if ( !sails.config.controllers.blueprints.expectIntegerId ) {
			return id;
		}

		// Ensure that id is numeric (unless this check is disabled)
		var castId = +id;
		if (id && util.isNaN(castId)) {

			// If it's not, move on to next middleware
			// but emit a console warning explaining the situation
			// (if the app is in development mode):
			if (sails.config.environment === 'development') {
				sails.log.warn('\n',
							'Just then, you were prevented from being routed \n',
							'to the `' + actionId + '` blueprint for controller: ' + controllerId +
							' using `id='+id+'`.\n',
							'This is because REST blueprint routes expect natural number ids by default, '+
							'and so the `' + actionId + '()` middleware was skipped- \n',
							'If you\'d like to disable this restriction, you can do so by setting \n',
							'sails.config.controllers.blueprints.expectIntegerId = false');
			}
			return false;
		}

		// Id is an integer
		return id;
	};
};

<|endoftext|>


<commit_before>'use strict';

/**
 * @file
 * This file is the entrypoint for the TopNavigation component
 */

import React from 'react';
import TopNavigation from './TopNavigation.component.js';

React.render(<TopNavigation/>, document.getElementById('topnavigation'), null);
<commit_msg>SSR Topnavigation<commit_after>'use strict';

/**
 * @file
 * This file is the entrypoint for the TopNavigation component
 */

import ReactDOM from 'react-dom';
import TopNavigation from './TopNavigation.component.js';

ReactDOM.render(<TopNavigation/>, document.getElementById('topnavigation'), null);
<|endoftext|>


<commit_before>/* global $ MB relEditor requests */
'use strict';
// ==UserScript==
// @name         MusicBrainz relation editor: Clone recording relations onto other recordings
// @namespace    mbz-loujine
// @author       loujine
// @version      2022.1.28
// @downloadURL  https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mb-reledit-clone_relations.user.js
// @updateURL    https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mb-reledit-clone_relations.user.js
// @supportURL   https://github.com/loujine/musicbrainz-scripts
// @icon         https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/icon.png
// @description  musicbrainz.org relation editor: Clone recording relations onto other recordings
// @compatible   firefox+tampermonkey
// @license      MIT
// @require      https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mbz-loujine-common.js
// @include      http*://*musicbrainz.org/release/*/edit-relationships
// @grant        none
// @run-at       document-end
// ==/UserScript==

const MBID_REGEX = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/;
const cloneIdxHelp = `Index(es) to clone relationships from.
Indexes start at 1 and are related to the list of selected recordings, not the tracklist indexes.
Indexes can be empty (= 1, first selected), a number n or a range n1-n2.
Relations are cloned by looping over the 'source' recordings (e.g. '1-2' on 7 selected recordings
will clone the relations of R1 to R3,R5,R7 and the ones from R2 to R4,R6
`;

function autoComplete(nodeId, entityType) {
    const $input = $(`input#${nodeId}`);
    const match = $input.val().match(MBID_REGEX);
    if (match) {
        const mbid = match[0];
        requests.GET(`/ws/2/${entityType}/${mbid}?fmt=json`, data => {
            data = JSON.parse(data);
            $input.data('mbid', mbid);
            $input.val(data.title || data.name);
            $input.css('background', '#bbffbb');
        });
    } else {
        $input.data().mbid = "";
        $input.css('background', '#ffaaaa');
    }
}

function autoCompleteRec() {
    return autoComplete('cloneExtRecording', 'recording');
}

function autoCompleteRel() {
    return autoComplete('cloneExtRelease', 'release');
}

function cloneAR(refIdx) {
    let startIdx;
    let range;
    if (refIdx.includes('-')) {
        startIdx = parseInt(refIdx.split('-')[0]) - 1;
        range = parseInt(refIdx.split('-')[1]) - startIdx;
    } else {
        startIdx = parseInt(refIdx) - 1 || 0;
        range = 1;
    }
    const vm = MB.releaseRelationshipEditor;
    const selectedRecordings = MB.relationshipEditor.UI.checkedRecordings();
    const sourceRecordings = selectedRecordings.splice(startIdx, range);
    let dialog;

    selectedRecordings.map((rec, idx) => {
        const sourceRecording = sourceRecordings[idx % sourceRecordings.length];
        const sourceRels = sourceRecording.relationships().filter(
            rel => !['recording-recording', 'recording-work'].includes(rel.entityTypes)
        );
        sourceRels.map(sourceRel => {
            dialog = new MB.relationshipEditor.UI.AddDialog({
                viewModel: vm,
                source: rec,
                target: sourceRel.entities().filter(ent => ent.entityType !== 'recording')[0],
            });
            dialog.accept();  // apparently required for Firefox

            dialog.relationship().linkTypeID(sourceRel.linkTypeID());
            const attrs = sourceRel.attributes();
            attrs.map(attr => {
                if (attr.creditedAs()) {
                    attr.credited_as = attr.creditedAs();
                }
            });
            dialog.relationship().setAttributes(attrs);

            dialog.relationship().entity0_credit(sourceRel.entity0_credit());
            dialog.relationship().entity1_credit(sourceRel.entity1_credit());

            if (sourceRel.begin_date) {
                dialog.relationship().begin_date.year(sourceRel.begin_date.year());
                dialog.relationship().begin_date.month(sourceRel.begin_date.month());
                dialog.relationship().begin_date.day(sourceRel.begin_date.day());
            }
            if (sourceRel.end_date) {
                dialog.relationship().end_date.year(sourceRel.end_date.year());
                dialog.relationship().end_date.month(sourceRel.end_date.month());
                dialog.relationship().end_date.day(sourceRel.end_date.day());
            }
            dialog.accept();
        });
    });
}

function cloneExtAR(recMBID) {
    if (recMBID.split('/').length > 1) {
        recMBID = recMBID.split('/')[4];
    }
    const vm = MB.releaseRelationshipEditor;
    const selectedRecordings = MB.relationshipEditor.UI.checkedRecordings();
    let dialog;

    requests.GET(`/ws/js/entity/${recMBID}?inc=rels`, resp => {
        const sourceRels = JSON.parse(resp).relationships.filter(
            rel => rel.target_type != 'work'
        );
        selectedRecordings.map(rec => {
            sourceRels.map(sourceRel => {
                dialog = new MB.relationshipEditor.UI.AddDialog({
                    viewModel: vm,
                    source: rec,
                    target: sourceRel.target,
                });
                dialog.accept();  // apparently required for Firefox

                dialog.relationship().linkTypeID(sourceRel.linkTypeID);
                dialog.relationship().setAttributes(sourceRel.attributes);

                dialog.relationship().entity0_credit(sourceRel.entity0_credit);
                dialog.relationship().entity1_credit(sourceRel.entity1_credit);

                if (sourceRel.target_type === 'recording'
                        && sourceRel.backward) {
                    dialog.changeDirection();
                }
                if (sourceRel.begin_date) {
                    dialog.relationship().begin_date.year(sourceRel.begin_date.year);
                    dialog.relationship().begin_date.month(sourceRel.begin_date.month);
                    dialog.relationship().begin_date.day(sourceRel.begin_date.day);
                }
                if (sourceRel.end_date) {
                    dialog.relationship().end_date.year(sourceRel.end_date.year);
                    dialog.relationship().end_date.month(sourceRel.end_date.month);
                    dialog.relationship().end_date.day(sourceRel.end_date.day);
                }
                dialog.accept();
            });
        });
    });
}

function cloneReleaseExtAR(relMBID) {
    if (relMBID.split('/').length > 1) {
        relMBID = relMBID.split('/')[4];
    }
    const vm = MB.releaseRelationshipEditor;
    const release = MB.entity({entityType: 'release', gid: document.URL.split('/')[4]});
    let dialog;

    requests.GET(`/ws/js/entity/${relMBID}?inc=rels`, resp => {
        const sourceRels = JSON.parse(resp).relationships.filter(
            rel => rel.target_type != 'url'
        );
        sourceRels.map(sourceRel => {
            dialog = new MB.relationshipEditor.UI.AddDialog({
                viewModel: vm,
                source: release,
                target: sourceRel.target,
            });

            dialog.relationship().linkTypeID(sourceRel.linkTypeID);
            dialog.relationship().setAttributes(sourceRel.attributes);

            dialog.relationship().entity0_credit(sourceRel.entity0_credit);
            dialog.relationship().entity1_credit(sourceRel.entity1_credit);

            if (sourceRel.begin_date) {
                dialog.relationship().begin_date.year(sourceRel.begin_date.year);
                dialog.relationship().begin_date.month(sourceRel.begin_date.month);
                dialog.relationship().begin_date.day(sourceRel.begin_date.day);
            }
            if (sourceRel.end_date) {
                dialog.relationship().end_date.year(sourceRel.end_date.year);
                dialog.relationship().end_date.month(sourceRel.end_date.month);
                dialog.relationship().end_date.day(sourceRel.end_date.day);
            }
            dialog.accept();
        });
    });
}

(function displayToolbar() {
    relEditor.container(document.querySelector('div.tabs'))
             .insertAdjacentHTML('beforeend', `

            Clone recording relations to selected recordings

            Source recording index in selection:

          <input type="text" id="cloneRef" placeholder="empty or n or 'n1-n2'">
          🛈

          OR recording link:
          <input type="text" id="cloneExtRecording" placeholder="recording mbid">

          <input type="button" id="cloneAR" value="Apply">

            Clone release relations from another release

          release link:
          <input type="text" id="cloneExtRelease" placeholder="release mbid">

          <input type="button" id="cloneReleaseAR" value="Apply">

    `);
})();

$(document).ready(function () {
    $('input#cloneExtRecording').on('input', autoCompleteRec);
    $('input#cloneExtRelease').on('input', autoCompleteRel);
    let appliedNote = false;
    document.getElementById('cloneAR').addEventListener('click', () => {
        const recMBID = $('input#cloneExtRecording').data('mbid');
        if (recMBID) {
            cloneExtAR(recMBID);
        } else {
            const refIdx = document.getElementById('cloneRef').value;
            cloneAR(refIdx);
        }
        if (!appliedNote) {
            relEditor.editNote(GM_info.script);
            appliedNote = true;
        }
    });
    document.getElementById('cloneReleaseAR').addEventListener('click', () => {
        const relMBID = $('input#cloneExtRelease').data('mbid');
        if (relMBID) {
            cloneReleaseExtAR(relMBID);
        }
        if (!appliedNote) {
            relEditor.editNote(GM_info.script, 'Cloned from https://musicbrainz.org/release/' + relMBID);
            appliedNote = true;
        }
    });
    return false;
});
<commit_msg>[clone relations] Empty external recording field when using internal cloning<commit_after>/* global $ MB relEditor requests */
'use strict';
// ==UserScript==
// @name         MusicBrainz relation editor: Clone recording relations onto other recordings
// @namespace    mbz-loujine
// @author       loujine
// @version      2022.1.28
// @downloadURL  https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mb-reledit-clone_relations.user.js
// @updateURL    https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mb-reledit-clone_relations.user.js
// @supportURL   https://github.com/loujine/musicbrainz-scripts
// @icon         https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/icon.png
// @description  musicbrainz.org relation editor: Clone recording relations onto other recordings
// @compatible   firefox+tampermonkey
// @license      MIT
// @require      https://raw.githubusercontent.com/loujine/musicbrainz-scripts/master/mbz-loujine-common.js
// @include      http*://*musicbrainz.org/release/*/edit-relationships
// @grant        none
// @run-at       document-end
// ==/UserScript==

const MBID_REGEX = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/;
const cloneIdxHelp = `Index(es) to clone relationships from.
Indexes start at 1 and are related to the list of selected recordings, not the tracklist indexes.
Indexes can be empty (= 1, first selected), a number n or a range n1-n2.
Relations are cloned by looping over the 'source' recordings (e.g. '1-2' on 7 selected recordings
will clone the relations of R1 to R3,R5,R7 and the ones from R2 to R4,R6
`;

function autoComplete(nodeId, entityType) {
    const $input = $(`input#${nodeId}`);
    const match = $input.val().match(MBID_REGEX);
    if (match) {
        const mbid = match[0];
        requests.GET(`/ws/2/${entityType}/${mbid}?fmt=json`, data => {
            data = JSON.parse(data);
            $input.data('mbid', mbid);
            $input.val(data.title || data.name);
            $input.css('background', '#bbffbb');
        });
    } else {
        $input.data().mbid = "";
        $input.css('background', '#ffaaaa');
    }
}

function autoCompleteRec() {
    return autoComplete('cloneExtRecording', 'recording');
}

function autoCompleteRel() {
    return autoComplete('cloneExtRelease', 'release');
}

function cloneAR(refIdx) {
    let startIdx;
    let range;
    if (refIdx.includes('-')) {
        startIdx = parseInt(refIdx.split('-')[0]) - 1;
        range = parseInt(refIdx.split('-')[1]) - startIdx;
    } else {
        startIdx = parseInt(refIdx) - 1 || 0;
        range = 1;
    }
    const vm = MB.releaseRelationshipEditor;
    const selectedRecordings = MB.relationshipEditor.UI.checkedRecordings();
    const sourceRecordings = selectedRecordings.splice(startIdx, range);
    let dialog;

    selectedRecordings.map((rec, idx) => {
        const sourceRecording = sourceRecordings[idx % sourceRecordings.length];
        const sourceRels = sourceRecording.relationships().filter(
            rel => !['recording-recording', 'recording-work'].includes(rel.entityTypes)
        );
        sourceRels.map(sourceRel => {
            dialog = new MB.relationshipEditor.UI.AddDialog({
                viewModel: vm,
                source: rec,
                target: sourceRel.entities().filter(ent => ent.entityType !== 'recording')[0],
            });
            dialog.accept();  // apparently required for Firefox

            dialog.relationship().linkTypeID(sourceRel.linkTypeID());
            const attrs = sourceRel.attributes();
            attrs.map(attr => {
                if (attr.creditedAs()) {
                    attr.credited_as = attr.creditedAs();
                }
            });
            dialog.relationship().setAttributes(attrs);

            dialog.relationship().entity0_credit(sourceRel.entity0_credit());
            dialog.relationship().entity1_credit(sourceRel.entity1_credit());

            if (sourceRel.begin_date) {
                dialog.relationship().begin_date.year(sourceRel.begin_date.year());
                dialog.relationship().begin_date.month(sourceRel.begin_date.month());
                dialog.relationship().begin_date.day(sourceRel.begin_date.day());
            }
            if (sourceRel.end_date) {
                dialog.relationship().end_date.year(sourceRel.end_date.year());
                dialog.relationship().end_date.month(sourceRel.end_date.month());
                dialog.relationship().end_date.day(sourceRel.end_date.day());
            }
            dialog.accept();
        });
    });
}

function cloneExtAR(recMBID) {
    if (recMBID.split('/').length > 1) {
        recMBID = recMBID.split('/')[4];
    }
    const vm = MB.releaseRelationshipEditor;
    const selectedRecordings = MB.relationshipEditor.UI.checkedRecordings();
    let dialog;

    requests.GET(`/ws/js/entity/${recMBID}?inc=rels`, resp => {
        const sourceRels = JSON.parse(resp).relationships.filter(
            rel => rel.target_type != 'work'
        );
        selectedRecordings.map(rec => {
            sourceRels.map(sourceRel => {
                dialog = new MB.relationshipEditor.UI.AddDialog({
                    viewModel: vm,
                    source: rec,
                    target: sourceRel.target,
                });
                dialog.accept();  // apparently required for Firefox

                dialog.relationship().linkTypeID(sourceRel.linkTypeID);
                dialog.relationship().setAttributes(sourceRel.attributes);

                dialog.relationship().entity0_credit(sourceRel.entity0_credit);
                dialog.relationship().entity1_credit(sourceRel.entity1_credit);

                if (sourceRel.target_type === 'recording'
                        && sourceRel.backward) {
                    dialog.changeDirection();
                }
                if (sourceRel.begin_date) {
                    dialog.relationship().begin_date.year(sourceRel.begin_date.year);
                    dialog.relationship().begin_date.month(sourceRel.begin_date.month);
                    dialog.relationship().begin_date.day(sourceRel.begin_date.day);
                }
                if (sourceRel.end_date) {
                    dialog.relationship().end_date.year(sourceRel.end_date.year);
                    dialog.relationship().end_date.month(sourceRel.end_date.month);
                    dialog.relationship().end_date.day(sourceRel.end_date.day);
                }
                dialog.accept();
            });
        });
    });
}

function cloneReleaseExtAR(relMBID) {
    if (relMBID.split('/').length > 1) {
        relMBID = relMBID.split('/')[4];
    }
    const vm = MB.releaseRelationshipEditor;
    const release = MB.entity({entityType: 'release', gid: document.URL.split('/')[4]});
    let dialog;

    requests.GET(`/ws/js/entity/${relMBID}?inc=rels`, resp => {
        const sourceRels = JSON.parse(resp).relationships.filter(
            rel => rel.target_type != 'url'
        );
        sourceRels.map(sourceRel => {
            dialog = new MB.relationshipEditor.UI.AddDialog({
                viewModel: vm,
                source: release,
                target: sourceRel.target,
            });

            dialog.relationship().linkTypeID(sourceRel.linkTypeID);
            dialog.relationship().setAttributes(sourceRel.attributes);

            dialog.relationship().entity0_credit(sourceRel.entity0_credit);
            dialog.relationship().entity1_credit(sourceRel.entity1_credit);

            if (sourceRel.begin_date) {
                dialog.relationship().begin_date.year(sourceRel.begin_date.year);
                dialog.relationship().begin_date.month(sourceRel.begin_date.month);
                dialog.relationship().begin_date.day(sourceRel.begin_date.day);
            }
            if (sourceRel.end_date) {
                dialog.relationship().end_date.year(sourceRel.end_date.year);
                dialog.relationship().end_date.month(sourceRel.end_date.month);
                dialog.relationship().end_date.day(sourceRel.end_date.day);
            }
            dialog.accept();
        });
    });
}

(function displayToolbar() {
    relEditor.container(document.querySelector('div.tabs'))
             .insertAdjacentHTML('beforeend', `

            Clone recording relations to selected recordings

            Source recording index in selection:

          <input type="text" id="cloneRef" placeholder="empty or n or 'n1-n2'">
          🛈

          OR recording link:
          <input type="text" id="cloneExtRecording" placeholder="recording mbid">

          <input type="button" id="cloneAR" value="Apply">

            Clone release relations from another release

          release link:
          <input type="text" id="cloneExtRelease" placeholder="release mbid">

          <input type="button" id="cloneReleaseAR" value="Apply">

    `);
})();

$(document).ready(function () {
    $('input#cloneExtRecording').on('input', autoCompleteRec);
    $('input#cloneExtRelease').on('input', autoCompleteRel);
    $('input#cloneRef').on('input', () => {
        document.getElementById('cloneExtRecording').value = '';
        autoCompleteRec();
    });
    let appliedNote = false;
    document.getElementById('cloneAR').addEventListener('click', () => {
        const recMBID = $('input#cloneExtRecording').data('mbid');
        if (recMBID) {
            cloneExtAR(recMBID);
        } else {
            const refIdx = document.getElementById('cloneRef').value;
            cloneAR(refIdx);
        }
        if (!appliedNote) {
            relEditor.editNote(GM_info.script);
            appliedNote = true;
        }
    });
    document.getElementById('cloneReleaseAR').addEventListener('click', () => {
        const relMBID = $('input#cloneExtRelease').data('mbid');
        if (relMBID) {
            cloneReleaseExtAR(relMBID);
        }
        if (!appliedNote) {
            relEditor.editNote(GM_info.script, 'Cloned from https://musicbrainz.org/release/' + relMBID);
            appliedNote = true;
        }
    });
    return false;
});
<|endoftext|>


<commit_before><commit_msg>Handling status codes from Azure Blob Storage<commit_after><|endoftext|>


<commit_before>'use strict';

const assert = require('assert');
const CriticalSection = require('../index');
const ReleaseCounter = require('./releaseCounter');

let currentTime = 0;

function getTime() {
  return currentTime++;
}

function delay(interval = 0) {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  });
}

describe('CriticalSection using async', () => {
  context('single enter', () => {
    let entered;
    let section;

    beforeEach(async () => {
      entered = false;
      section = new CriticalSection();

      await section.enter();

      entered = true;
    });

    it('should enter successfully', () => {
      assert.equal(true, entered);
    });
  });

  context('enter twice simultaneously', () => {
    let counter;
    let section;
    let worker;

    beforeEach(() => {
      counter = new ReleaseCounter();
      section = new CriticalSection();
      worker = [];

      return Promise.all([
        (async () => {
          await counter.waitFor(0); // make sure this op enter first
          await section.enter();
          await counter.waitFor(3); // make sure 1 and 2 are blocking enter
          worker[0] = getTime();
          await section.leave();
        })(),
        (async () => {
          await counter.waitFor(1);
          await section.enter();
          worker[1] = getTime();
          await section.leave();
        })(),
        (async () => {
          await counter.waitFor(2);
          await section.enter();
          worker[2] = getTime();
          await section.leave();
        })()
      ]);
    });

    it('should have entered 3 times',                   () => assert.equal(3, worker.length));
    it('should let worker 1 enter first then worker 2', () => assert(worker[0] < worker[1]));
    it('should let worker 2 enter first then worker 3', () => assert(worker[1] < worker[2]));
  });

  context('enter twice in serial', () => {
    let worker;
    let section;

    beforeEach(async () => {
      worker = [];
      section = new CriticalSection();

      await section.enter();
      worker[0] = getTime();
      await section.leave();

      await section.enter();
      worker[1] = getTime();
      await section.leave();
    });

    it('should have entered twice',                     () => assert.equal(2, worker.length));
    it('should let worker 1 enter first then worker 2', () => assert(worker[0] < worker[1]));
  });

  context('only one operation can enter', () => {
    let counter;
    let log;
    let section;

    beforeEach(async () => {
      counter = new ReleaseCounter();
      log = '';
      section = new CriticalSection();

      return Promise.all([
        (async () => {
          await section.enter();
          await counter.waitFor(0); // make sure this op enter first
          log += 'E1';
          await counter.waitFor(2); // make sure 1 is blocking on enter
          log += 'L1';
          await section.leave();
        })(),
        (async () => {
          await counter.waitFor(1); // make sure 1 is in the block
          assert.equal('E1', log);
          await section.enter();
          assert.equal('E1L1', log);
          log += 'E2';
          log += 'L2';
          await section.leave();
        })()
      ]);
    });

    it('should allow only one operation at a time', () => assert.equal(log, 'E1L1E2L2'));
  });

  context('race condition', () => {
    const numRaces = 100;
    let logs;
    let section;

    beforeEach(async () => {
      logs = [];
      section = new CriticalSection();

      const tasks = [];

      for (let i = 0; i < numRaces; i++) {
        tasks.push(async () => {
          await section.enter();
          logs.push(`E${ i }`);
          await delay(1);
          logs.push(`L${ i }`);
          await section.leave();
        });
      }

      return Promise.all(tasks.map(task => task()));
    });

    it('should allow only one operation at a time', () => {
      for (let i = 0; i < numRaces; i++) {
        assert(logs[i * 2] === `E${ i }`);
        assert(logs[i * 2 + 1] === `L${ i }`);
      }
    });
  });
});
<commit_msg>Test against slow Promise<commit_after>'use strict';

const assert = require('assert');
const CriticalSection = require('../index');
const ReleaseCounter = require('./releaseCounter');

let currentTime = 0;

function getTime() {
  return currentTime++;
}

function delay(interval = 0) {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  });
}

describe('CriticalSection using async', () => {
  context('single enter', () => {
    let entered;
    let section;

    beforeEach(async () => {
      entered = false;
      section = new CriticalSection();

      await section.enter();

      entered = true;
    });

    it('should enter successfully', () => {
      assert.equal(true, entered);
    });
  });

  context('enter twice simultaneously', () => {
    let counter;
    let section;
    let worker;

    beforeEach(() => {
      counter = new ReleaseCounter();
      section = new CriticalSection();
      worker = [];

      return Promise.all([
        (async () => {
          await counter.waitFor(0); // make sure this op enter first
          await section.enter();
          await counter.waitFor(3); // make sure 1 and 2 are blocking enter
          worker[0] = getTime();
          await section.leave();
        })(),
        (async () => {
          await counter.waitFor(1);
          await section.enter();
          worker[1] = getTime();
          await section.leave();
        })(),
        (async () => {
          await counter.waitFor(2);
          await section.enter();
          worker[2] = getTime();
          await section.leave();
        })()
      ]);
    });

    it('should have entered 3 times',                   () => assert.equal(3, worker.length));
    it('should let worker 1 enter first then worker 2', () => assert(worker[0] < worker[1]));
    it('should let worker 2 enter first then worker 3', () => assert(worker[1] < worker[2]));
  });

  context('enter twice in serial', () => {
    let worker;
    let section;

    beforeEach(async () => {
      worker = [];
      section = new CriticalSection();

      await section.enter();
      worker[0] = getTime();
      await section.leave();

      await section.enter();
      worker[1] = getTime();
      await section.leave();
    });

    it('should have entered twice',                     () => assert.equal(2, worker.length));
    it('should let worker 1 enter first then worker 2', () => assert(worker[0] < worker[1]));
  });

  context('only one operation can enter', () => {
    let counter;
    let log;
    let section;

    beforeEach(async () => {
      counter = new ReleaseCounter();
      log = '';
      section = new CriticalSection();

      return Promise.all([
        (async () => {
          await section.enter();
          await counter.waitFor(0); // make sure this op enter first
          log += 'E1';
          await counter.waitFor(2); // make sure 1 is blocking on enter
          log += 'L1';
          await section.leave();
        })(),
        (async () => {
          await counter.waitFor(1); // make sure 1 is in the block
          assert.equal('E1', log);
          await section.enter();
          assert.equal('E1L1', log);
          log += 'E2';
          log += 'L2';
          await section.leave();
        })()
      ]);
    });

    it('should allow only one operation at a time', () => assert.equal(log, 'E1L1E2L2'));
  });

  context('race condition', () => {
    const numRaces = 100;
    let logs;
    let section;

    beforeEach(async () => {
      logs = [];
      section = new CriticalSection();

      const tasks = [];

      for (let i = 0; i < numRaces; i++) {
        tasks.push(async () => {
          await section.enter();
          logs.push(`E${ i }`);
          await delay(1);
          logs.push(`L${ i }`);
          await section.leave();
        });
      }

      return Promise.all(tasks.map(task => task()));
    });

    it('should allow only one operation at a time', () => {
      for (let i = 0; i < numRaces; i++) {
        assert(logs[i * 2] === `E${ i }`);
        assert(logs[i * 2 + 1] === `L${ i }`);
      }
    });
  });

  context('slow promise', () => {
    let log;
    let section;

    beforeEach(() => {
      log = '';
      section = new CriticalSection({ Promise: SlowPromise });

      return Promise.all([
        (async () => {
          await section.enter();
          log += 'E1';
          log += 'L1';
          await section.leave();
        })(),
        (async () => {
          await section.enter();
          log += 'E2';
          log += 'L2';
          await section.leave();
        })()
      ]);
    });

    it('should enter in correct order', () => {
      assert.equal('E1L1E2L2', log);
    });
  });
});

class SlowPromise {
  constructor(fn) {
    return new Promise((resolve, reject) => {
      setImmediate(() => {
        fn(resolve, reject);
      });
    });
  }
}

SlowPromise.resolve = result => Promise.resolve(result);
SlowPromise.reject = error => Promise.reject(error);
<|endoftext|>


<commit_before>'use strict';
/**
 * @file
 * Currently the main entrypoint served on /work.
 * Provides the work view for the enduser.
 */
import React from 'react';
import Reflux from 'reflux';

import {isEmpty, values} from 'lodash';

import workAction from '../../actions/Work.action.js';
import WorkStore from '../../stores/Work.store.js';
import {CoverImage, OrderButton} from 'dbc-react-components';
import {rewriteCoverImageUrl} from '../../../utils/CoverImage.util.js';
import LikeContainer from '../LikeDislike/LikeContainer.component.js';
import DislikeContainer from '../LikeDislike/DislikeContainer.component.js';
import ProfileStore from '../../stores/Profile.store.js';

const Work = React.createClass({
  displayName: 'Work.component',

  propTypes: {
    id: React.PropTypes.string,
    work: React.PropTypes.object
  },

  mixins: [
    Reflux.connect(WorkStore, 'work'),
    Reflux.connect(ProfileStore, 'profile')
  ],

  getInitialState() {
    if (typeof window === 'undefined') {
      this.getWork();
    }
  },

  componentDidMount() {
    this.getWork();
  },

  getWork() {
    if (this.props.work) {
      workAction.updated([this.props.work]);
    }
    workAction({id: this.props.id});
  },

  getLikeDislikeContainers(id) {
    return (

          <LikeContainer objectId={id} />

          <DislikeContainer objectId={id} />

    );
  },

  render() {
    const profile = this.state.profile;
    const work = this.props.work ? this.props.work : this.state.work;
    const id = this.props.id;

    // No result found!
    if (work.info.hits === '0') {
      return (
Værket blev ikke fundet
);
    }

    // Result is pending, waiting for serviceprovider
    if (isEmpty(work.result) && isEmpty(work.info)) {
      return (

      );
    }

    // Empty result (Should perhaps be an error report)
    if (isEmpty(work.result)) {
      return (
);
    }

    // Data was found, begin rendering
    const likeContainers = this.state.profile.userIsLoggedIn ? this.getLikeDislikeContainers(id) : ' ';
    let editions = [];
    let specifics_object = {};

    work.result.editions.forEach((element, index) => {
      let edition = (

{element.type}

{element.edition + (element.edition !== '' ? ', ' : '')} {element.date}

{element.extent}

            {element.isbns.length > 0 ? 'ISBN: ' : ''}
            {element.isbns.join(', ')}

            {element.link.map((link, idx) => {
              return (Find online);
            })}

      );

      editions.push(edition);

      const elementKey = element.accessType + '_' + element.workType;

      if (!specifics_object.hasOwnProperty(elementKey)) {
        specifics_object[elementKey] = {
          accessType: element.accessType,
          creator: element.creator,
          dates: [],
          identifiers: [],
          links: [],
          order: '',
          title: element.title,
          type: element.type
        };
      }

      element.link.forEach((link) => {
        specifics_object[elementKey].links.push(link);
      });
      specifics_object[elementKey].dates.push(element.date);
      specifics_object[elementKey].identifiers.push(element.identifier);
      specifics_object[elementKey].order = '/order?ids=' + specifics_object[elementKey].identifiers.join(',') +
        '&creator=' + specifics_object[elementKey].creator +
        '&title=' + encodeURIComponent(specifics_object[elementKey].title) +
        '&type=' + specifics_object[elementKey].type;
    });

    let specifics = values(specifics_object);

    return (

          <CoverImage pids={[id, work.result.pid]} prefSize='detail_500' rewritwImgUrl={rewriteCoverImageUrl} />

          <OrderButton manifestations={specifics} profile={profile} />

          {likeContainers}

{work.result.title}

                {work.result.creator}

{work.result.abstract}

              {work.result.isbns.length > 0 ? 'ISBN: '+ work.result.isbns[0] : ''}

{work.result.extent}

                {work.result.actors.length > 0 ? 'Medvirkende: ' : ''}
                {work.result.actors.map((actor, index) => {
                  return (

                        {actor}

                    );
                })}

              {work.result.series}

              {work.result.subjects.length > 0 ? 'Emner: ' : ''}
              {work.result.subjects.map((subject, index, array) => {
                const notLast = (array.length - 1) !== index;
                return (

                     {notLast ? subject + ', ' : subject}

                );
              })}

              {work.result.dk5s.length > 0 ? 'Opstilling: ' : ''}
              {work.result.dk5s.map((dk5, index) => {
                return (

                    {dk5.text}

                );
              })}

{work.result.audience.age.join(', ')}

{work.result.audience.pegi}

{work.result.audience.medieraad}

              {work.result.tracks.length > 0 ? 'Trackliste: ' : ''}
              {work.result.tracks.map((track, index) => {
                return (
{track}
);
              })}

              {work.result.languages.length > 0 ? 'Sprog: ' : ''}
              {work.result.languages.join(', ')}

            {specifics.map((specific, index) => {
              return (
                {specific.type} ({specific.dates.join('; ')})
              );
            })}

            {editions.length > 0 ? 'Udgaver:' : ''}
            {editions}

    );
  }
});

export default Work;
<commit_msg>Accumulated commit of the following from branch 'origin/ready/us107':<commit_after>'use strict';
/**
 * @file
 * Currently the main entrypoint served on /work.
 * Provides the work view for the enduser.
 */
import React from 'react';
import Reflux from 'reflux';

import {isEmpty, values} from 'lodash';

import workAction from '../../actions/Work.action.js';
import WorkStore from '../../stores/Work.store.js';
import {CoverImage, OrderButton} from 'dbc-react-components';
import {rewriteCoverImageUrl} from '../../../utils/CoverImage.util.js';
import LikeContainer from '../LikeDislike/LikeContainer.component.js';
import DislikeContainer from '../LikeDislike/DislikeContainer.component.js';
import ProfileStore from '../../stores/Profile.store.js';

const Work = React.createClass({
  displayName: 'Work.component',

  propTypes: {
    id: React.PropTypes.string,
    work: React.PropTypes.object
  },

  mixins: [
    Reflux.connect(WorkStore, 'work'),
    Reflux.connect(ProfileStore, 'profile')
  ],

  getInitialState() {
    if (typeof window === 'undefined') {
      this.getWork();
    }
  },

  componentDidMount() {
    this.getWork();
  },

  getWork() {
    if (this.props.work) {
      workAction.updated([this.props.work]);
    }
    workAction({id: this.props.id});
  },

  getLikeDislikeContainers(id) {
    return (

          <LikeContainer objectId={id} />

          <DislikeContainer objectId={id} />

    );
  },

  render() {
    const profile = this.state.profile;
    const work = this.props.work ? this.props.work : this.state.work;
    const id = this.props.id;

    // No result found!
    if (work.info.hits === '0') {
      return (
Værket blev ikke fundet
);
    }

    // Result is pending, waiting for serviceprovider
    if (isEmpty(work.result) && isEmpty(work.info)) {
      return (

      );
    }

    // Empty result (Should perhaps be an error report)
    if (isEmpty(work.result)) {
      return (
);
    }

    // Data was found, begin rendering
    const likeContainers = this.state.profile.userIsLoggedIn ? this.getLikeDislikeContainers(id) : ' ';
    let editions = [];
    let specifics_object = {};

    work.result.editions.forEach((element, index) => {
      let edition = (

{element.type}

{element.edition + (element.edition !== '' ? ', ' : '')} {element.date}

{element.extent}

            {element.isbns.length > 0 ? 'ISBN: ' : ''}
            {element.isbns.join(', ')}

            {element.link.map((link, idx) => {
              return (Find online);
            })}

      );

      editions.push(edition);

      const elementKey = element.accessType + '_' + element.type;

      if (!specifics_object.hasOwnProperty(elementKey)) {
        specifics_object[elementKey] = {
          accessType: element.accessType,
          creator: element.creator,
          dates: [],
          identifiers: [],
          links: [],
          order: '',
          title: element.title,
          type: element.type
        };
      }

      element.link.forEach((link) => {
        specifics_object[elementKey].links.push(link);
      });
      specifics_object[elementKey].dates.push(element.date);
      specifics_object[elementKey].identifiers.push(element.identifier);
      specifics_object[elementKey].order = '/order?ids=' + specifics_object[elementKey].identifiers.join(',') +
        '&creator=' + specifics_object[elementKey].creator +
        '&title=' + encodeURIComponent(specifics_object[elementKey].title) +
        '&type=' + specifics_object[elementKey].type;
    });

    let specifics = values(specifics_object);

    return (

          <CoverImage pids={[id, work.result.pid]} prefSize='detail_500' rewritwImgUrl={rewriteCoverImageUrl} />

          <OrderButton manifestations={specifics} profile={profile} />

          {likeContainers}

{work.result.title}

                {work.result.creator}

{work.result.abstract}

              {work.result.isbns.length > 0 ? 'ISBN: '+ work.result.isbns[0] : ''}

{work.result.extent}

                {work.result.actors.length > 0 ? 'Medvirkende: ' : ''}
                {work.result.actors.map((actor, index) => {
                  return (

                        {actor}

                    );
                })}

              {work.result.series}

              {work.result.subjects.length > 0 ? 'Emner: ' : ''}
              {work.result.subjects.map((subject, index, array) => {
                const notLast = (array.length - 1) !== index;
                return (

                     {notLast ? subject + ', ' : subject}

                );
              })}

              {work.result.dk5s.length > 0 ? 'Opstilling: ' : ''}
              {work.result.dk5s.map((dk5, index) => {
                return (

                    {dk5.text}

                );
              })}

{work.result.audience.age.join(', ')}

{work.result.audience.pegi}

{work.result.audience.medieraad}

              {work.result.tracks.length > 0 ? 'Trackliste: ' : ''}
              {work.result.tracks.map((track, index) => {
                return (
{track}
);
              })}

              {work.result.languages.length > 0 ? 'Sprog: ' : ''}
              {work.result.languages.join(', ')}

            {specifics.map((specific, index) => {
              return (
                {specific.type} ({specific.dates.join('; ')})
              );
            })}

            {editions.length > 0 ? 'Udgaver:' : ''}
            {editions}

    );
  }
});

export default Work;
<|endoftext|>


<commit_before>/* globals Image, HTMLElement, XMLHttpRequest, MutationObserver */

'use strict'

;(function (global) {
  function Dom (selector) {
    if (!(this instanceof Dom)) {
      return new Dom(selector)
    }

    if (typeof selector === 'string') {
      this.element = document.querySelector(selector)
    } else if (selector instanceof HTMLElement) {
      this.element = selector
    } else {
      throw new TypeError('Selector must be a string or an HTMLElement')
    }

    return this
  }

  Dom.prototype.parent = function () {
    return new Dom(this.element.parentNode)
  }

  Dom.prototype.remove = function () {
    return this.element.parentNode.removeChild(this.element)
  }

  Dom.prototype.replace = function (newEle, oldEle) {
    this.element.replaceChild(newEle, oldEle)
    return this
  }

  Dom.prototype.append = function (element) {
    this.element.appendChild(element)
    return this
  }

  Dom.prototype.css = function (rule) {
    rule = rule.replace(/-([a-z])/ig, function (all, letter) {
      return letter.toUpperCase()
    })
               .replace(/\n/g, '')
               .split(';')
               .filter(function (line) {
                 return line.trim() !== ''
               })
    for (var i = 0; i < rule.length; i++) {
      this.element.style[rule[i].split(':')[0].trim()] = rule[i].split(':')[1].trim()
    }

    return this
  }

  Dom.prototype.removeClass = function (className) {
    this.element.classList.remove(className)
    return this
  }

  Dom.prototype.addClass = function (className) {
    this.element.classList.add(className)
    return this
  }

  Dom.prototype.classList = function () {
    var classList = []
    for (var ele in this.element.classList) {
      classList.push(this.element.classList[ele])
    }
    return classList
  }

  Dom.prototype.toggleClass = function (className) {
    this.element.classList.toggle(className)
    return this
  }

  Dom.prototype.hasClass = function (className) {
    return this.classList().indexOf(className) > -1
  }

  Dom.prototype.removeChildren = function () {
    while (this.element.firstChild) {
      this.element.removeChild(this.element.firstChild)
    }
    return this
  }

  Dom.prototype.on = function (eventName, callback) {
    this.element.addEventListener(eventName, callback.bind(this))
    return this
  }

  Dom.prototype.off = function (eventName, callback) {
    if (eventName && callback) {
      this.element.removeEventListener(eventName, callback)
    } else {
      var newEle = this.clone(true)
      this.parent().replace(newEle, this.element)
    }

    return this
  }

  Dom.prototype.text = function (text) {
    if (!text) {
      return this.element.textContent
    }
    this.element.textContent = text
    return this
  }

  Dom.prototype.html = function (html) {
    if (!html) {
      return this.element.innerHTML
    }
    this.element.innerHTML = html
    return this
  }

  Dom.prototype.value = function (value) {
    if (typeof value !== 'string') {
      return this.element.value
    }
    this.element.value = value
    return this
  }

  Dom.prototype.attr = function (name, value) {
    if (!value) {
      return this.element.getAttribute(name)
    }
    this.element.setAttribute(name, value)
    return this
  }

  Dom.prototype.removeAttr = function (name) {
    this.element.removeAttribute(name)
    return this
  }

  Dom.prototype.hasAttr = function (name) {
    return this.element.hasAttribute(name)
  }

  Dom.prototype.test = function (regex) {
    return regex.test(this.element.value)
  }

  Dom.prototype.clone = function (deep) {
    return this.element.cloneNode(deep || false)
  }

  Dom.prototype.observe = function (options, callback) {
    this.observer = new MutationObserver(callback.bind(this))
    this.observer.observe(this.element, options)
    return this
  }

  Dom.prototype.removeObserver = function () {
    if (this.observer) {
      this.observer.disconnect()
    }
    this.observer = null
    return this
  }

  Dom.get = function (selector) {
    if (typeof selector !== 'string') {
      throw new TypeError('Selector must be a string')
    }
    return document.querySelector(selector)
  }

  Dom.ajax = function (options) {
    var xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function () {
      if (xhr.readyState === XMLHttpRequest.DONE && (options.local ? xhr.status === 200 || xhr.status === 0 : xhr.status === 200)) {
        if (options.json) {
          try {
            return options.callback(null, JSON.parse(xhr.responseText))
          } catch (err) {
            return options.callback(err, null)
          }
        } else {
          return options.callback(null, xhr.responseText)
        }
      } else {
        options.callback({ err: 'Fetch failed', status: xhr.status, state: xhr.readyState }, null)
      }
    }
    xhr.open(options.method, options.url)
    xhr.send()
  }

  Dom.parse = function (html) {
    var temp = document.createElement('TEMPLATE')
    temp.innerHTML = html
    return temp.content
  }

  Dom.image = function (path, alt) {
    var image = new Image()
    image.src = path
    image.alt = alt || ''
    return image
  }

  Dom.prototype.swipe = function (callback) {
    this.element.addEventListener('touchstart', handleTouchStart, false)
    this.element.addEventListener('touchmove', handleTouchMove, false)
    callback = callback.bind(this)

    var xDown = null
    var yDown = null

    function handleTouchStart (evt) {
      xDown = evt.touches[0].clientX
      yDown = evt.touches[0].clientY
    }

    function handleTouchMove (evt) {
      if (!xDown || !yDown) {
        return
      }
      var direction = null

      var xUp = evt.touches[0].clientX
      var yUp = evt.touches[0].clientY

      var xDiff = xDown - xUp
      var yDiff = yDown - yUp

      if (Math.abs(xDiff) > Math.abs(yDiff)) {
        direction = xDiff > 0 ? 'left' : 'right'
      } else {
        direction = yDiff > 0 ? 'up' : 'down'
      }

      xDown = null
      yDown = null
      callback(direction)
    }

    return this
  }

  global.dom = Dom
  global.$ = global.$ || Dom
})(window)
<commit_msg>Fix if the element does not exist<commit_after>/* globals Image, HTMLElement, XMLHttpRequest, MutationObserver */

'use strict'

;(function (global) {
  function Dom (selector) {
    if (!(this instanceof Dom)) {
      return new Dom(selector)
    }

    if (typeof selector === 'string') {
      this.element = document.querySelector(selector)
    } else if (selector instanceof HTMLElement) {
      this.element = selector
    } else {
      throw new TypeError('Selector must be a string or an HTMLElement')
    }

    if (this.element === null) {
      console.log('Dom.js: The element \'' + selector + '\' you selected does not exist!')
      this.element = document.createDocumentFragment()
    }

    return this
  }

  Dom.prototype.parent = function () {
    return new Dom(this.element.parentNode)
  }

  Dom.prototype.remove = function () {
    return this.element.parentNode.removeChild(this.element)
  }

  Dom.prototype.replace = function (newEle, oldEle) {
    this.element.replaceChild(newEle, oldEle)
    return this
  }

  Dom.prototype.append = function (element) {
    this.element.appendChild(element)
    return this
  }

  Dom.prototype.css = function (rule) {
    rule = rule.replace(/-([a-z])/ig, function (all, letter) {
      return letter.toUpperCase()
    })
               .replace(/\n/g, '')
               .split(';')
               .filter(function (line) {
                 return line.trim() !== ''
               })
    for (var i = 0; i < rule.length; i++) {
      this.element.style[rule[i].split(':')[0].trim()] = rule[i].split(':')[1].trim()
    }

    return this
  }

  Dom.prototype.removeClass = function (className) {
    this.element.classList.remove(className)
    return this
  }

  Dom.prototype.addClass = function (className) {
    this.element.classList.add(className)
    return this
  }

  Dom.prototype.classList = function () {
    var classList = []
    for (var ele in this.element.classList) {
      classList.push(this.element.classList[ele])
    }
    return classList
  }

  Dom.prototype.toggleClass = function (className) {
    this.element.classList.toggle(className)
    return this
  }

  Dom.prototype.hasClass = function (className) {
    return this.classList().indexOf(className) > -1
  }

  Dom.prototype.removeChildren = function () {
    while (this.element.firstChild) {
      this.element.removeChild(this.element.firstChild)
    }
    return this
  }

  Dom.prototype.on = function (eventName, callback) {
    this.element.addEventListener(eventName, callback.bind(this))
    return this
  }

  Dom.prototype.off = function (eventName, callback) {
    if (eventName && callback) {
      this.element.removeEventListener(eventName, callback)
    } else {
      var newEle = this.clone(true)
      this.parent().replace(newEle, this.element)
    }

    return this
  }

  Dom.prototype.text = function (text) {
    if (!text) {
      return this.element.textContent
    }
    this.element.textContent = text
    return this
  }

  Dom.prototype.html = function (html) {
    if (!html) {
      return this.element.innerHTML
    }
    this.element.innerHTML = html
    return this
  }

  Dom.prototype.value = function (value) {
    if (typeof value !== 'string') {
      return this.element.value
    }
    this.element.value = value
    return this
  }

  Dom.prototype.attr = function (name, value) {
    if (!value) {
      return this.element.getAttribute(name)
    }
    this.element.setAttribute(name, value)
    return this
  }

  Dom.prototype.removeAttr = function (name) {
    this.element.removeAttribute(name)
    return this
  }

  Dom.prototype.hasAttr = function (name) {
    return this.element.hasAttribute(name)
  }

  Dom.prototype.test = function (regex) {
    return regex.test(this.element.value)
  }

  Dom.prototype.clone = function (deep) {
    return this.element.cloneNode(deep || false)
  }

  Dom.prototype.observe = function (options, callback) {
    this.observer = new MutationObserver(callback.bind(this))
    this.observer.observe(this.element, options)
    return this
  }

  Dom.prototype.removeObserver = function () {
    if (this.observer) {
      this.observer.disconnect()
    }
    this.observer = null
    return this
  }

  Dom.get = function (selector) {
    if (typeof selector !== 'string') {
      throw new TypeError('Selector must be a string')
    }
    return document.querySelector(selector)
  }

  Dom.ajax = function (options) {
    var xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function () {
      if (xhr.readyState === XMLHttpRequest.DONE && (options.local ? xhr.status === 200 || xhr.status === 0 : xhr.status === 200)) {
        if (options.json) {
          try {
            return options.callback(null, JSON.parse(xhr.responseText))
          } catch (err) {
            return options.callback(err, null)
          }
        } else {
          return options.callback(null, xhr.responseText)
        }
      } else {
        options.callback({ err: 'Fetch failed', status: xhr.status, state: xhr.readyState }, null)
      }
    }
    xhr.open(options.method, options.url)
    xhr.send()
  }

  Dom.parse = function (html) {
    var temp = document.createElement('TEMPLATE')
    temp.innerHTML = html
    return temp.content
  }

  Dom.image = function (path, alt) {
    var image = new Image()
    image.src = path
    image.alt = alt || ''
    return image
  }

  Dom.prototype.swipe = function (callback) {
    this.element.addEventListener('touchstart', handleTouchStart, false)
    this.element.addEventListener('touchmove', handleTouchMove, false)
    callback = callback.bind(this)

    var xDown = null
    var yDown = null

    function handleTouchStart (evt) {
      xDown = evt.touches[0].clientX
      yDown = evt.touches[0].clientY
    }

    function handleTouchMove (evt) {
      if (!xDown || !yDown) {
        return
      }
      var direction = null

      var xUp = evt.touches[0].clientX
      var yUp = evt.touches[0].clientY

      var xDiff = xDown - xUp
      var yDiff = yDown - yUp

      if (Math.abs(xDiff) > Math.abs(yDiff)) {
        direction = xDiff > 0 ? 'left' : 'right'
      } else {
        direction = yDiff > 0 ? 'up' : 'down'
      }

      xDown = null
      yDown = null
      callback(direction)
    }

    return this
  }

  global.dom = Dom
  global.$ = global.$ || Dom
})(window)
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Type for loading a playing sounds, works on multiple platforms and supports
 * embedded base64 data.  This uses Web Audio when available, primarily
 * because the webkit platforms were failing with cross-domain errors when
 * attempting to load audio data from embedded data URIs.  This was occurring
 * in mid-September 2013.  Simplification may be possible if the cross-domain
 * issue goes away at some point in the future.
 *
 * TODO: Do we want to specify just the stem of the file name and have this
 * code figure out whether to use mp3 or ogg?
 */
define( function( require ) {
  'use strict';

  // Imports
  var base64Binary = require( 'VIBE/base64Binary' );
  var platform = require( 'PHET_CORE/platform' );

  // Constants
  var SILENCE_PATH = 'sounds/empty.mp3';

  // Set up a single audio context that will be used by all sounds when
  // using Web Audio API.
  var audioContext;
  if ( 'AudioContext' in window ) {
    audioContext = new AudioContext();
  }
  else if ( 'webkitAudioContext' in window ) {
    audioContext = new webkitAudioContext();
  }

  /**
   * @param soundName - name of this sound no path name, e.g. "ding.mp3".
   * @constructor
   */
  function Sound( soundName ) {

    var self = this;

    // Locate the sound data.
    this.sound = document.getElementById( soundName );
    if ( this.sound !== null ) {
      // Sound data is already in the DOM.  For PhET's purposes, it should be
      // encoded as base64 data.  If it's not, throw an error.
      if ( this.sound.getAttribute( 'src' ).match( /^data/ ) === null ) {
        throw new Error( "Embedded audio data must be encoded as base64." );
      }
    }
    else {
      // Set up an audio element in the DOM with a relative path.
      this.sound = document.createElement( 'audio' );
      this.sound.setAttribute( 'src', soundName );
      this.sound.load();
    }

    if ( this.sound === null ) {
      // Sound not found.
      throw new Error( "The specified sound was not found: " + soundName );
    }

    // Load the sound. TODO: Consider moving this to sim preload phase.
    if ( audioContext ) {
      var arrayBuff;
      if ( this.sound.getAttribute( 'src' ).match( /^data:/ ) !== null ) {
        // We're working with base64 data, so we need to decode it.
        var soundData = this.sound.getAttribute( 'src' ).replace( new RegExp( '^.*,' ), '' );
        arrayBuff = base64Binary.decodeArrayBuffer( soundData );
        audioContext.decodeAudioData( arrayBuff,
          function( audioData ) {
            self.audioBuffer = audioData;
          },
          function() {
            console.log( "Error: Unable to decode audio data." );
          } );
      }
      else {
        // Sound is not yet in DOM, try loading via relative URL path.
        var request = new XMLHttpRequest();
        request.open( 'GET', soundName, true );
        request.responseType = 'arraybuffer';
        request.onload = function() {
          // Decode the audio data asynchronously
          audioContext.decodeAudioData( request.response,
            function( audioData ) {
              self.audioBuffer = audioData;
            },
            function() { console.log( "Error loading and decoding sound, sound name: " + soundName ); }
          );
        };
        request.onerror = function() {
          console.log( "Error occurred on request (delete this code when debugged)" );
        };

        request.send();
      }
    }
  }

  /**
   * Plays the sound using the Web Audio API if available or HTML5 audio if not.
   */
  Sound.prototype.play = function() {
    if ( audioContext ) {
      // Use the Web Audio API.
      this.soundSource = audioContext.createBufferSource();
      this.soundSource.buffer = this.audioBuffer;
      this.soundSource.connect( audioContext.destination );

      if ( 'AudioContext' in window ) {
        this.soundSource.start( 0 );
      }
      else if ( 'webkitAudioContext' in window ) {
        this.soundSource.noteOn( 0 );
      }
    }
    else {
      // Use the HTML5 API.
      this.sound.play();
    }
  };

  /**
   * Stop the sound if it is currently playing.
   */
  Sound.prototype.stop = function() {
    if ( 'AudioContext' in window ) {
      // TODO: TBD
    }
    else if ( 'webkitAudioContext' in window ) {
      // TODO: TBD
    }
    else {
      this.sound.pause();
      this.sound.currentTime = 0;
    }
  };

  // Workaround for iOS+Safari: In this situation, we must play an audio file
  // from a thread initiated by a user event such as touchstart before any
  // sounds will play. This is impossible with scenery, since all scenery
  // events are batched and dispatched from the animation loop.
  // See http://stackoverflow.com/questions/12517000/no-sound-on-ios-6-web-audio-api
  // Note: This requires the user to touch the screen before audio can be played
  if ( platform.mobileSafari ) {
    var silenceDomNode = document.createElement( 'audio' );
    silenceDomNode.setAttribute( 'id', SILENCE_PATH );
    // Base64 encoded silence, taken from empty.mp3, created by PhET.
    silenceDomNode.setAttribute( 'src', 'data:audio/mpeg;base64,//s0wAAAAAABLgAAACAAACXAAAAE//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////s0wD2AAAABLgAAACAAACXAAAAE//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAA//s0wHsAAAABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wLiAAAABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wPYADQABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wP+AD2ABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' );
    document.body.appendChild( silenceDomNode );

    var silence = new Sound( SILENCE_PATH );
    var playSilence = function() {
      silence.play();
      window.removeEventListener( 'touchstart', playSilence, false );
    };
    window.addEventListener( 'touchstart', playSilence, false );
  }

  return Sound;
} );
<commit_msg>added a requirejs plugin for audio<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Type for loading a playing sounds, works on multiple platforms and supports
 * embedded base64 data.  This uses Web Audio when available, primarily
 * because the webkit platforms were failing with cross-domain errors when
 * attempting to load audio data from embedded data URIs.  This was occurring
 * in mid-September 2013.  Simplification may be possible if the cross-domain
 * issue goes away at some point in the future.
 *
 * TODO: Do we want to specify just the stem of the file name and have this
 * code figure out whether to use mp3 or ogg?
 */
define( function( require ) {
  'use strict';

  // Imports
  var base64Binary = require( 'VIBE/base64Binary' );
  var platform = require( 'PHET_CORE/platform' );

  // Constants
  var SILENCE_PATH = 'sounds/empty.mp3';

  // Set up a single audio context that will be used by all sounds when
  // using Web Audio API.
  var audioContext;
  if ( 'AudioContext' in window ) {
    audioContext = new AudioContext();
  }
  else if ( 'webkitAudioContext' in window ) {
    audioContext = new webkitAudioContext();
  }

  /**
   * @param {String|{url|base64}} soundURL - name of this sound no path name, e.g. "ding.mp3".
   * If it is an object, it has either a url or base64 field, depending on whether it is running at requirejs development time or build time.
   * @constructor
   */
  function Sound( soundName ) {
    var soundURL = soundName;

    //TODO: Switch over to this style for the constructor once clients are all using it.
    if ( typeof(soundName) === 'object' ) {

      //Load the path from the requirejs development option
      if ( soundName.url ) {
        soundURL = soundName.url;
      }

      //Load the base64 text
      else if ( soundName.base64 ) {
        var base64 = soundName.base64;

      }
      else {
        throw new Error( 'Sound argument incorrect' );
      }
    }

    var self = this;

    // Locate the sound data.
    this.sound = document.getElementById( soundURL );
    if ( this.sound !== null ) {
      // Sound data is already in the DOM.  For PhET's purposes, it should be
      // encoded as base64 data.  If it's not, throw an error.
      if ( this.sound.getAttribute( 'src' ).match( /^data/ ) === null ) {
        throw new Error( "Embedded audio data must be encoded as base64." );
      }
    }
    else {
      // Set up an audio element in the DOM with a relative path.
      //TODO: This is for backward compatibility, can be deleted once we have cut over to audio! plugin
      if ( !base64 ) {
        this.sound = document.createElement( 'audio' );
        this.sound.setAttribute( 'src', soundURL );
        this.sound.load();
      }
    }

    if ( this.sound === null && !base64 ) {
      // Sound not found.
      throw new Error( "The specified sound was not found: " + soundURL );
    }

    // Load the sound. TODO: Consider moving this to sim preload phase.
    if ( audioContext ) {
      var arrayBuff;

//      debugger;
      if ( base64 || this.sound.getAttribute( 'src' ).match( /^data:/ ) !== null ) {
        // We're working with base64 data, so we need to decode it.

        //The regular expression removes the mime header
        var soundData = (base64 ? base64 : this.sound.getAttribute( 'src' )).replace( new RegExp( '^.*,' ), '' );
        arrayBuff = base64Binary.decodeArrayBuffer( soundData );
        audioContext.decodeAudioData( arrayBuff,
          function( audioData ) {
//            debugger;
            self.audioBuffer = audioData;
          },
          function() {
            console.log( "Error: Unable to decode audio data." );
          } );
      }
      else {
        // Sound is not yet in DOM, try loading via relative URL path.
        var request = new XMLHttpRequest();
        request.open( 'GET', soundURL, true );
        request.responseType = 'arraybuffer';
        request.onload = function() {
          // Decode the audio data asynchronously
          audioContext.decodeAudioData( request.response,
            function( audioData ) {
              self.audioBuffer = audioData;
            },
            function() { console.log( "Error loading and decoding sound, sound name: " + soundURL ); }
          );
        };
        request.onerror = function() {
          console.log( "Error occurred on request (delete this code when debugged)" );
        };

        request.send();
      }
    }
  }

  /**
   * Plays the sound using the Web Audio API if available or HTML5 audio if not.
   */
  Sound.prototype.play = function() {
    if ( audioContext ) {
      // Use the Web Audio API.
      this.soundSource = audioContext.createBufferSource();
      this.soundSource.buffer = this.audioBuffer;
      this.soundSource.connect( audioContext.destination );

      if ( 'AudioContext' in window ) {
        this.soundSource.start( 0 );
      }
      else if ( 'webkitAudioContext' in window ) {
        this.soundSource.noteOn( 0 );
      }
    }
    else {
      // Use the HTML5 API.
      this.sound.play();
    }
  };

  /**
   * Stop the sound if it is currently playing.
   */
  Sound.prototype.stop = function() {
    if ( 'AudioContext' in window ) {
      // TODO: TBD
    }
    else if ( 'webkitAudioContext' in window ) {
      // TODO: TBD
    }
    else {
      this.sound.pause();
      this.sound.currentTime = 0;
    }
  };

  // Workaround for iOS+Safari: In this situation, we must play an audio file
  // from a thread initiated by a user event such as touchstart before any
  // sounds will play. This is impossible with scenery, since all scenery
  // events are batched and dispatched from the animation loop.
  // See http://stackoverflow.com/questions/12517000/no-sound-on-ios-6-web-audio-api
  // Note: This requires the user to touch the screen before audio can be played
  if ( platform.mobileSafari ) {
    var silenceDomNode = document.createElement( 'audio' );
    silenceDomNode.setAttribute( 'id', SILENCE_PATH );
    // Base64 encoded silence, taken from empty.mp3, created by PhET.
    silenceDomNode.setAttribute( 'src', 'data:audio/mpeg;base64,//s0wAAAAAABLgAAACAAACXAAAAE//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////s0wD2AAAABLgAAACAAACXAAAAE//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAA//s0wHsAAAABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wLiAAAABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wPYADQABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wP+AD2ABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' );
    document.body.appendChild( silenceDomNode );

    var silence = new Sound( SILENCE_PATH );
    var playSilence = function() {
      silence.play();
      window.removeEventListener( 'touchstart', playSilence, false );
    };
    window.addEventListener( 'touchstart', playSilence, false );
  }

  return Sound;
} );
<|endoftext|>