<commit_before>'use strict'

var path = require('path')
var test = require('tape')
var fs = require('fs')
var createTempFile = require('../')
require('string.prototype.startswith')
require('string.prototype.endswith')

function ctf (ext) {
  var ws = createTempFile(ext)
  ws.on('error', function (err) {
    throw err
  })
  return ws
}

test('write stream works', function (t) {
  t.plan(2)
  var ws = ctf()
  fs.createReadStream(__dirname + '/do-not-change.txt')
    .pipe(ws)
    .on('finish', testWS)

  function testWS () {
    fs.readFile(ws.path, { encoding: 'utf8' }, function (err, string) {
      t.notOk(err, err ? err.message : 'no error')
      t.ok(string.startsWith('do not delete or change this file'), 'strings match')
      ws.cleanupSync()
      t.end()
    })
  }
})

test('extension (backward compatibility)', function (t) {
  t.plan(1)
  var ws = ctf('.txt')
  t.equal(path.extname(ws.path), '.txt', 'file extension is ".txt"')
  ws.end('lolz')
  ws.cleanupSync()
  t.end()
})

test('extension', function (t) {
  t.plan(1)
  var ws = ctf({ext: '.txt'})
  t.equal(path.extname(ws.path), '.txt', 'file extension is ".txt"')
  ws.end('lolz')
  ws.cleanupSync()
  t.end()
})

function errorHandling (opts, method, codes) {
  return function errhandle (t) {
    var ws = createTempFile(opts)

    ws.on('error', function (e) {
      t.notEqual(codes.indexOf(e.code), -1, 'Got ' + codes.join('/') + ' error')
      t.end()
    })
    setTimeout(function () {
      try {
        fs.unlinkSync(ws.path)
      } catch (e) {
        console.log('here')
        t.fail(String(e))
      }
      setTimeout(ws[method], 10)
    }, 10)
  }
}

test('error handling sync default params', errorHandling({}, 'cleanupSync', ['EPERM', 'ENOENT']))
test('error handling sync explicit enoent', errorHandling({enoent: true}, 'cleanupSync', ['EPERM', 'ENOENT']))

test('error handling async default params', errorHandling({}, 'cleanup', ['EPERM', 'ENOENT']))
test('error handling async explicit enoent', errorHandling({enoent: true}, 'cleanup', ['EPERM', 'ENOENT']))

test('cleanupSync() works', function (t) {
  t.plan(3)

  var ws = ctf()
  ws.end('lolz')

  setTimeout(function () { // timeout makes this test much more robust
    t.ok(fs.existsSync(ws.path), 'created file')
    t.doesNotThrow(ws.cleanupSync)
    t.notOk(fs.existsSync(ws.path), 'cleanupSync() deleted the file')
    t.end()
  }, 0)
})

test('cleanup() works', function (t) {
  t.plan(3)

  var ws = ctf()
  ws.end('lolz')

  setTimeout(function () { // timeout makes this test much more robust
    t.ok(fs.existsSync(ws.path), 'created file')
    ws.cleanup(function (err) {
      t.notOk(err, err ? err.message : 'no error during cleanup()')
      t.notOk(fs.existsSync(ws.path), 'cleanup() deleted the file')
      t.end()
    })
  }, 0)
})

function callsEnd (method) {
  return function (t) {
    t.plan(1)
    var ws = ctf()
    ws.on('finish', function () {
      t.pass('ws.end() was called')
      t.end()
    })
    ws.write('lolz')
    setTimeout(ws[method], 0)
  }
}

callsEnd('what')
test('ws.end is called when cleanup is called and stream is still going', callsEnd('cleanup'))
test('ws.end is called when cleanupSync is called and stream is still going', callsEnd('cleanupSync'))
<commit_msg>Refactor tests<commit_after>'use strict'

var path = require('path')
var test = require('tape')
var fs = require('fs')
var createTempFile = require('../')
require('string.prototype.startswith')
require('string.prototype.endswith')

const noop = function () {}

function ctf (ext) {
  var ws = createTempFile(ext)
  ws.on('error', function (err) {
    throw err
  })
  return ws
}

test('write stream works', function (t) {
  t.plan(2)
  var ws = ctf()
  fs
    .createReadStream(path.join(__dirname, '/do-not-change.txt'))
    .pipe(ws)
    .on('finish', testWS)

  function testWS () {
    fs.readFile(ws.path, {encoding: 'utf8'}, function (err, string) {
      t.notOk(err, err ? err.message : 'no error')
      t.ok(
        string.startsWith('do not delete or change this file'),
        'strings match'
      )
      ws.cleanupSync()
      t.end()
    })
  }
})

test('extension (backward compatibility)', function (t) {
  t.plan(1)
  var ws = ctf('.txt')
  t.equal(path.extname(ws.path), '.txt', 'file extension is ".txt"')
  ws.end('lolz')
  ws.cleanupSync()
  t.end()
})

test('extension', function (t) {
  t.plan(1)
  var ws = ctf({ext: '.txt'})
  t.equal(path.extname(ws.path), '.txt', 'file extension is ".txt"')
  ws.end('lolz')
  ws.cleanupSync()
  t.end()
})

function errorHandlingSync (opts, method, codes) {
  return function errhandle (t) {
    var ws = createTempFile(opts)

    ws.on('error', function (e) {
      t.notEqual(codes.indexOf(e.code), -1, 'Got ' + codes.join('/') + ' error')
      t.end()
    })
    setTimeout(function () {
      try {
        fs.unlinkSync(ws.path)
      } catch (e) {
        t.fail(String(e))
      }
      setTimeout(ws[method], 10)
    }, 10)
  }
}

function errorHandling (opts, method, codes) {
  return function errhandle (t) {
    var ws = createTempFile(opts)

    ws.on('error', function (e) {
      t.notEqual(codes.indexOf(e.code), -1, 'Got ' + codes.join('/') + ' error')
      t.end()
    })
    setTimeout(function () {
      try {
        fs.unlinkSync(ws.path)
      } catch (e) {
        t.fail(String(e))
      }
      setTimeout(ws[method], 10, noop)
    }, 10)
  }
}

test(
  'error handling sync default params',
  errorHandlingSync({}, 'cleanupSync', ['EPERM', 'ENOENT'])
)
test(
  'error handling sync explicit enoent',
  errorHandlingSync({enoent: true}, 'cleanupSync', ['EPERM', 'ENOENT'])
)

test(
  'error handling callback async default params',
  errorHandling({}, 'cleanup', ['EPERM', 'ENOENT'])
)

test(
  'error handling callback async explicit enoent',
  errorHandling({enoent: true}, 'cleanup', ['EPERM', 'ENOENT'])
)

test('cleanupSync() works', function (t) {
  t.plan(3)

  var ws = ctf()
  ws.end('lolz')

  setTimeout(function () {
    // timeout makes this test much more robust
    t.ok(fs.existsSync(ws.path), 'created file')
    t.doesNotThrow(ws.cleanupSync)
    t.notOk(fs.existsSync(ws.path), 'cleanupSync() deleted the file')
    t.end()
  }, 0)
})

test('cleanup() works', function (t) {
  t.plan(3)

  var ws = ctf()
  ws.end('lolz')

  setTimeout(function () {
    // timeout makes this test much more robust
    t.ok(fs.existsSync(ws.path), 'created file')
    ws.cleanup(function (err) {
      t.notOk(err, err ? err.message : 'no error during cleanup()')
      t.notOk(fs.existsSync(ws.path), 'cleanup() deleted the file')
      t.end()
    })
  }, 0)
})

function callsEndSync (method) {
  return function (t) {
    t.plan(1)
    var ws = ctf()
    ws.on('finish', function () {
      t.pass('ws.end() was called')
      t.end()
    })
    ws.write('lolz')
    setTimeout(ws[method], 0)
  }
}

function callsEnd (method) {
  return function (t) {
    t.plan(1)
    var ws = ctf()
    ws.on('finish', function () {
      t.pass('ws.end() was called')
      t.end()
    })
    ws.write('lolz')
    setTimeout(ws[method], 0, noop)
  }
}

callsEndSync('what')

test(
  'ws.end is called when cleanup is called and stream is still going',
  callsEnd('cleanup')
)
test(
  'ws.end is called when cleanupSync is called and stream is still going',
  callsEndSync('cleanupSync')
)
<|endoftext|>


<commit_before>// Copyright 2013-2015, University of Colorado Boulder
/**
 * Launches a PhET Simulation, after preloading the specified images.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var checkNamespaces = require( 'JOIST/checkNamespaces' );
  var joist = require( 'JOIST/joist' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Random = require( 'DOT/Random' );
  var Brand = require( 'BRAND/Brand' );

  // phet-io modules
  var SimIFrameAPI = require( 'ifphetio!PHET_IO/SimIFrameAPI' );
  var TPhETIO = require( 'ifphetio!PHET_IO/types/TPhETIO' );
  var phetio = require( 'ifphetio!PHET_IO/phetio' );

  var SimLauncher = {

    /**
     * Launch the Sim by preloading the images and calling the callback.
     *
     * @param callback the callback function which should create and start the sim, given that the images are loaded
     * @public - to be called by main()s everywhere
     */
    launch: function( callback ) {
      assert && assert( !window.phet.launchCalled, 'Tried to launch twice' );

      //Signify that the SimLauncher was called, see https://github.com/phetsims/joist/issues/142
      window.phet.joist.launchCalled = true;

      // image elements to remove once we are fully loaded
      var elementsToRemove = [];

      function doneLoadingImages() {

        window.phetLaunchSimulation = function() {

          // Register all of the static tandems.
          Tandem.launch();

          // Provide a global Random that is easy to use and seedable from phet-io for playback
          // phet-io configuration happens after SimLauncher.launch is called and before phetLaunchSimulation is called
          phet.joist.random = new Random( { staticSeed: true } );

          // Instantiate the sim and show it.
          callback();
        };

        // PhET-iO simulations support an initialization phase (before the sim launches)
        if ( Brand.phetioEnabled ) {
          new Tandem( 'phetio' ).addInstance( phetio, TPhETIO );
          SimIFrameAPI.initialize(); // calls back to window.phetLaunchSimulation
        }

        if ( phet.phetio && !phet.phetio.queryParameters.phetioStandalone ) {

          // Wait for phet-io to finish adding listeners. It will direct the launch from there.
        }
        else {
          window.phetLaunchSimulation();
        }
      }

      // if image dimensions exist, immediately fire the "all images loaded" event
      var loaded = 0;

      // Taken from http://stackoverflow.com/questions/1977871/check-if-an-image-is-loaded-no-errors-in-javascript
      function isImageOK( img ) {

        // During the onload event, IE correctly identifies any images that
        // weren’t downloaded as not complete. Others should too. Gecko-based
        // browsers act like NS4 in that they report this incorrectly.
        if ( !img.complete ) {
          return false;
        }

        // However, they do have two very useful properties: naturalWidth and
        // naturalHeight. These give the true size of the image. If it failed
        // to load, either of these should be zero.
        if ( typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0 ) {
          return false;
        }

        // No other way of checking: assume it’s ok.
        return true;
      }

      //For the images that were written to base64 format using requirejs, make sure they are loaded.
      //img.src = base64 is asynchronous on IE10 and OSX/Safari, so we have to make sure they loaded before returning.
      if ( window.phetImages ) {
        for ( var i = 0; i < window.phetImages.length; i++ ) {
          var phetImage = window.phetImages[ i ];

          // For built versions that use phet-io, the simulation may have already loaded all of the images, so
          // check them here before scheduling them for load.
          if ( isImageOK( phetImage ) ) {
            loaded++;
            if ( loaded === window.phetImages.length ) {
              doneLoadingImages();
            }
          }
          else {
            phetImage.onload = function() {
              loaded++;
              if ( loaded === window.phetImages.length ) {
                doneLoadingImages();
              }
            };
          }

        }
      }
      else {
        doneLoadingImages();
      }

      $( window ).load( function() {
        // if images were not loaded immediately, signal the "all images loaded" event

        // we wait for here to remove the images from the DOM, otherwise IE9/10 treat the images as completely blank!
        _.each( elementsToRemove, function( element ) {

          //TODO: Why is this null sometimes?  see https://github.com/phetsims/joist/issues/388
          if ( element.parentNode ) {
            element.parentNode.removeChild( element );
          }
        } );
      } );

      // Check namespaces if assertions are enabled, see https://github.com/phetsims/joist/issues/307.
      assert && checkNamespaces();
    }
  };

  joist.register( 'SimLauncher', SimLauncher );

  return SimLauncher;
} );<commit_msg>unify brand check to phet.phetio, https://github.com/phetsims/phet-io/issues/1106<commit_after>// Copyright 2013-2015, University of Colorado Boulder
/**
 * Launches a PhET Simulation, after preloading the specified images.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var checkNamespaces = require( 'JOIST/checkNamespaces' );
  var joist = require( 'JOIST/joist' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Random = require( 'DOT/Random' );

  // phet-io modules
  var SimIFrameAPI = require( 'ifphetio!PHET_IO/SimIFrameAPI' );
  var TPhETIO = require( 'ifphetio!PHET_IO/types/TPhETIO' );
  var phetio = require( 'ifphetio!PHET_IO/phetio' );

  var SimLauncher = {

    /**
     * Launch the Sim by preloading the images and calling the callback.
     *
     * @param callback the callback function which should create and start the sim, given that the images are loaded
     * @public - to be called by main()s everywhere
     */
    launch: function( callback ) {
      assert && assert( !window.phet.launchCalled, 'Tried to launch twice' );

      //Signify that the SimLauncher was called, see https://github.com/phetsims/joist/issues/142
      window.phet.joist.launchCalled = true;

      // image elements to remove once we are fully loaded
      var elementsToRemove = [];

      function doneLoadingImages() {

        window.phetLaunchSimulation = function() {

          // Register all of the static tandems.
          Tandem.launch();

          // Provide a global Random that is easy to use and seedable from phet-io for playback
          // phet-io configuration happens after SimLauncher.launch is called and before phetLaunchSimulation is called
          phet.joist.random = new Random( { staticSeed: true } );

          // Instantiate the sim and show it.
          callback();
        };

        // PhET-iO simulations support an initialization phase (before the sim launches)
        if ( phet.phetio ) {
          new Tandem( 'phetio' ).addInstance( phetio, TPhETIO );
          SimIFrameAPI.initialize(); // calls back to window.phetLaunchSimulation
        }

        if ( phet.phetio && !phet.phetio.queryParameters.phetioStandalone ) {

          // Wait for phet-io to finish adding listeners. It will direct the launch from there.
        }
        else {
          window.phetLaunchSimulation();
        }
      }

      // if image dimensions exist, immediately fire the "all images loaded" event
      var loaded = 0;

      // Taken from http://stackoverflow.com/questions/1977871/check-if-an-image-is-loaded-no-errors-in-javascript
      function isImageOK( img ) {

        // During the onload event, IE correctly identifies any images that
        // weren’t downloaded as not complete. Others should too. Gecko-based
        // browsers act like NS4 in that they report this incorrectly.
        if ( !img.complete ) {
          return false;
        }

        // However, they do have two very useful properties: naturalWidth and
        // naturalHeight. These give the true size of the image. If it failed
        // to load, either of these should be zero.
        if ( typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0 ) {
          return false;
        }

        // No other way of checking: assume it’s ok.
        return true;
      }

      //For the images that were written to base64 format using requirejs, make sure they are loaded.
      //img.src = base64 is asynchronous on IE10 and OSX/Safari, so we have to make sure they loaded before returning.
      if ( window.phetImages ) {
        for ( var i = 0; i < window.phetImages.length; i++ ) {
          var phetImage = window.phetImages[ i ];

          // For built versions that use phet-io, the simulation may have already loaded all of the images, so
          // check them here before scheduling them for load.
          if ( isImageOK( phetImage ) ) {
            loaded++;
            if ( loaded === window.phetImages.length ) {
              doneLoadingImages();
            }
          }
          else {
            phetImage.onload = function() {
              loaded++;
              if ( loaded === window.phetImages.length ) {
                doneLoadingImages();
              }
            };
          }

        }
      }
      else {
        doneLoadingImages();
      }

      $( window ).load( function() {
        // if images were not loaded immediately, signal the "all images loaded" event

        // we wait for here to remove the images from the DOM, otherwise IE9/10 treat the images as completely blank!
        _.each( elementsToRemove, function( element ) {

          //TODO: Why is this null sometimes?  see https://github.com/phetsims/joist/issues/388
          if ( element.parentNode ) {
            element.parentNode.removeChild( element );
          }
        } );
      } );

      // Check namespaces if assertions are enabled, see https://github.com/phetsims/joist/issues/307.
      assert && checkNamespaces();
    }
  };

  joist.register( 'SimLauncher', SimLauncher );

  return SimLauncher;
} );<|endoftext|>


<commit_before>(function () {
'use strict';
 window.ModeBar = ModeBar;

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this,
        groupedButtons = config.buttons;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = [];

    groupedButtons.forEach( function (buttons) {
        var group = _this.createGroup();

        buttons.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttons.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    _this.updateActiveButton();

}

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Empty div for containing a group of buttons
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttons.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo.layout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Click handler for cartesian type plots
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo.layout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr==='zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoomCartesian: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    panCartesian: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomInCartesian: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOutCartesian: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScaleCartesian: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosestCartesian: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompareCartesian: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    }
};

})();
<commit_msg>check for existing modebar element in case no layout available<commit_after>(function () {

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this,
        groupedButtons = config.buttons;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = [];

    groupedButtons.forEach( function (buttons) {
        var group = _this.createGroup();

        buttons.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttons.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

window.ModeBar = ModeBar;

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Empty div for containing a group of buttons
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttons.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo.layout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Click handler for cartesian type plots
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo.layout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr==='zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
};

/**
 * Click handler for 3d Hover mode
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleHover3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo.layout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (scene) {
        scene._webgl.spikeEnable = !scene._webgl.spikeEnable;
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
};

/**
 * Click handler for 3d camera modes
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handle3dCamera (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo.layout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (scene) {
        if ('_webgl' in scene && 'camera' in scene._webgl) {
            scene._webgl.camera.keyBindingMode = attr;
        }
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoom2d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    pan2d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomIn2d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOut2d: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScale2d: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosest2d: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompare2d: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    },
    zoom3d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handle3dCamera
    },
    pan3d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handle3dCamera
    },
    rotate3d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handle3dCamera
    },
    closest3d: {
        title: 'Toggle show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleHover3d
    }

};

})();
<|endoftext|>


<commit_before>//React Component import
import React, {Component} from 'react';
//Bootstrap import
import {Glyphicon, Modal, Button, Form, FormGroup, ControlLabel, FormControl,} from 'react-bootstrap';
//Project component import
import {getMonth} from '../DateFunctions'
import CalendarDateBox from './CalendarDateBox';
//CSS import
import '../../styles/modal.css';
import {SUITED_FOR_TYPES} from './SuitedForPicker';
import StarRatingComponent from "react-star-rating-component";
import {
    signupActivity,
    signoffActivity,
    checkIfSignedUp,
    postNewRating,
    postNewComment,
    getComments
} from "../APIFunctions";

class ActivityModal extends Component {

    constructor(props) {
        super(props);
        this.state = {
            show: false,
            hasChecked: false,
            attending: false,
            loggedIn: false,
            fetchedComments: false,
            comments: []
        }
    }

    componentWillReceiveProps(props) {
        this.setState({
            show: props.show
        });
    }

    showMap = () => {
        window.open('https://www.google.no/maps/place/' + this.props.activity.location, '_blank');
    };

    editActivity = () => {
        window.location = "/activity/" + this.props.id;
    };

    closeActivityModal = () => {
        this.setState({
            show: false,
            hasChecked: false,
            fetchedComments: false
        });
    };

    onSignup = () => {
        const request = {
            id: this.props.id
        };
        signupActivity(request).then((response) => {
            if (response.status == 204) {
                this.setState({
                    attending: true
                });
            }
        })
    };

    onSignOf = () => {
        const request = {
            id: this.props.id
        };
        signoffActivity(request).then((response) => {
            if (response.status == 210) {
                this.setState({
                    attending: false
                });
            }
        })
    };

    checkIfSignUp = () => {
        const request = {
            id: this.props.id
        };
        checkIfSignedUp(request).then((response) => {
            if (response.status == 204) {
                this.setState({
                    attending: true,
                    hasChecked: true,
                    loggedIn: true
                });
            } else if (response.status == 205) {
                this.setState({
                    loggedIn: true,
                    hasChecked: true
                })
            } else if (response == 206) {
                this.setState({
                    loggedIn: false
                })
            }
        })
    };

    onRateChange = (nextValue, prevValue, name) => {
        const obj = {
            id: this.props.id,
            rating: nextValue
        };
        postNewRating(obj)
    };

    fetchComments = () => {
        getComments(this.props.id).then((result) => {
            this.setState({
                comments: result.reverse(),
                fetchedComments: true,
            });
        });
    };

    onPostComment = () => {
        const obj = {
            id: this.props.id,
            comment2: $("#commentInput").val()
        };
        $("#commentInput").val("");
        postNewComment(obj);
        this.fetchComments();
    };

    render() {
        const {date, activityName, activityType, suitedForType, provider, adaptions, age, time_start, time_end, location, description, videos, images, rating, number_of_ratings} = this.props.activity;

        const starRating = rating / number_of_ratings;
        let suitedForContainer = [];
        if (suitedForType >= 0) {
            suitedForContainer = SUITED_FOR_TYPES.filter(type => parseInt(type.value) === suitedForType)[0];
        }

        //let videoContainer = null;
        //if (videos.length > 0) {
        //    const videos = this.state.videos.map((video, i) => {
        //        const path = "/media/video/" + video;
        //        return (
        //            <video className="modal-video" controls="controls" key={i}>
        //                <source src={path}/>
        //            </video>
        //        )
        //    });
        //    videoContainer =
        //

        //
Video fra arrangementet

        //            {videos}
        //
;
        //}

        let imageContainer = null;
        if (images.length > 0) {
            imageContainer =

Bilder fra arrangementet

;
        }

        if (this.state.show && !this.state.hasChecked) {
            this.checkIfSignUp()
        }

        let attendingContainer = null;
        let ratingContainer = null;
        let postCommentContainer = null;

        if (!this.state.loggedIn) {
            attendingContainer =

Påmelding til {activityName}

Du må være logget inn for å kunne melde deg på dette arrangementet

;
        } else if (this.state.attending == false) {
            attendingContainer =

Påmelding til {activityName}

                        <Button className="btn btn-success" onClick={this.onSignup}>Meld på!</Button>

;

        } else {
            attendingContainer =

Du er påmeldt {activityName}

                        <Button onClick={this.onSignOf} className="btn btn-danger">Meld av</Button>

;
        }
        if (this.state.loggedIn) {
            ratingContainer =
                <StarRatingComponent id="activityRating" name="activityRating" emptyStarColor="#BBB" value={starRating}
                                     onStarClick={this.onRateChange.bind(this)}/>;

            postCommentContainer =

                    <form className="comment-form" method="POST" action="/postComment/">

                            <textarea placeholder="Skriv inn din kommentar her" id="commentInput"
                                      className="form-control custom-control"></textarea>
                            Send

                    </form>

;
        }

        if (this.state.noComments) {

        }

        if (this.state.show && !this.state.fetchedComments) {
            this.fetchComments()
        }

        let allComments = this.state.comments;

        return (
            <Modal
                show={this.state.show}
                onHide={this.closeActivityModal}
                bsSize="large"
                aria-labelledby="contained-modal-title-lg">
                <Modal.Header closeButton>
                    <Modal.Title id="contained-modal-title-lg">
                        <CalendarDateBox date={new Date(date)}/>

{activityName}

Arrangeres av: {provider} {ratingContainer}

                    </Modal.Title>
                </Modal.Header>
                <Modal.Body>

                        Dette arrangementet er tilpasset for: {suitedForContainer.label}

<Glyphicon glyph="glyphicon glyphicon-user"/>
                                Alder: {age}

<Glyphicon glyph="glyphicon glyphicon-time"/>
                                Tid: {time_start} - {time_end}

                                <Glyphicon glyph="glyphicon glyphicon-map-marker"/>
                                Sted: {location}

                                Vis på kart

                        {attendingContainer}

Om arrangementet

{description}

                    {/* videoContainer */}
                    {imageContainer}

Kommentarer

                    {postCommentContainer}

                        {allComments.map((com, i) =>

{com.fields.date}

{com.fields.comment}

                        )}

                </Modal.Body>
                <Modal.Footer>
                    <Button onClick={this.editActivity}>Endre aktivitet</Button>
                    <Button onClick={this.closeActivityModal}>Lukk</Button>
                    <Button onClick={this.fetchComments}>Kommentarer</Button>
                </Modal.Footer>
            </Modal>
        )
    }
}

export default ActivityModal;
<commit_msg>small design fix on comments<commit_after>//React Component import
import React, {Component} from 'react';
//Bootstrap import
import {Glyphicon, Modal, Button, Form, FormGroup, ControlLabel, FormControl,} from 'react-bootstrap';
//Project component import
import {getMonth} from '../DateFunctions'
import CalendarDateBox from './CalendarDateBox';
//CSS import
import '../../styles/modal.css';
import {SUITED_FOR_TYPES} from './SuitedForPicker';
import StarRatingComponent from "react-star-rating-component";
import {
    signupActivity,
    signoffActivity,
    checkIfSignedUp,
    postNewRating,
    postNewComment,
    getComments
} from "../APIFunctions";

class ActivityModal extends Component {

    constructor(props) {
        super(props);
        this.state = {
            show: false,
            hasChecked: false,
            attending: false,
            loggedIn: false,
            fetchedComments: false,
            comments: []
        }
    }

    componentWillReceiveProps(props) {
        this.setState({
            show: props.show
        });
    }

    showMap = () => {
        window.open('https://www.google.no/maps/place/' + this.props.activity.location, '_blank');
    };

    editActivity = () => {
        window.location = "/activity/" + this.props.id;
    };

    closeActivityModal = () => {
        this.setState({
            show: false,
            hasChecked: false,
            fetchedComments: false
        });
    };

    onSignup = () => {
        const request = {
            id: this.props.id
        };
        signupActivity(request).then((response) => {
            if (response.status == 204) {
                this.setState({
                    attending: true
                });
            }
        })
    };

    onSignOf = () => {
        const request = {
            id: this.props.id
        };
        signoffActivity(request).then((response) => {
            if (response.status == 210) {
                this.setState({
                    attending: false
                });
            }
        })
    };

    checkIfSignUp = () => {
        const request = {
            id: this.props.id
        };
        checkIfSignedUp(request).then((response) => {
            if (response.status == 204) {
                this.setState({
                    attending: true,
                    hasChecked: true,
                    loggedIn: true
                });
            } else if (response.status == 205) {
                this.setState({
                    loggedIn: true,
                    hasChecked: true
                })
            } else if (response == 206) {
                this.setState({
                    loggedIn: false
                })
            }
        })
    };

    onRateChange = (nextValue, prevValue, name) => {
        const obj = {
            id: this.props.id,
            rating: nextValue
        };
        postNewRating(obj)
    };

    fetchComments = () => {
        getComments(this.props.id).then((result) => {
            this.setState({
                comments: result.reverse(),
                fetchedComments: true,
            });

        });
    };

    onPostComment = () => {
        const obj = {
            id: this.props.id,
            comment2: $("#commentInput").val()
        };
        $("#commentInput").val("");
        postNewComment(obj);
        this.fetchComments();
    };

    render() {
        const {date, activityName, activityType, suitedForType, provider, adaptions, age, time_start, time_end, location, description, videos, images, rating, number_of_ratings} = this.props.activity;

        const starRating = rating / number_of_ratings;
        let suitedForContainer = [];
        if (suitedForType >= 0) {
            suitedForContainer = SUITED_FOR_TYPES.filter(type => parseInt(type.value) === suitedForType)[0];
        }

        //let videoContainer = null;
        //if (videos.length > 0) {
        //    const videos = this.state.videos.map((video, i) => {
        //        const path = "/media/video/" + video;
        //        return (
        //            <video className="modal-video" controls="controls" key={i}>
        //                <source src={path}/>
        //            </video>
        //        )
        //    });
        //    videoContainer =
        //

        //
Video fra arrangementet

        //            {videos}
        //
;
        //}

        let imageContainer = null;
        if (images.length > 0) {
            imageContainer =

Bilder fra arrangementet

;
        }

        if (this.state.show && !this.state.hasChecked) {
            this.checkIfSignUp()
        }

        let attendingContainer = null;
        let ratingContainer = null;
        let postCommentContainer = null;

        if (!this.state.loggedIn) {
            attendingContainer =

Påmelding til {activityName}

Du må være logget inn for å kunne melde deg på dette arrangementet

;
        } else if (this.state.attending == false) {
            attendingContainer =

Påmelding til {activityName}

                        <Button className="btn btn-success" onClick={this.onSignup}>Meld på!</Button>

;

        } else {
            attendingContainer =

Du er påmeldt {activityName}

                        <Button onClick={this.onSignOf} className="btn btn-danger">Meld av</Button>

;
        }
        if (this.state.loggedIn) {
            ratingContainer =
                <StarRatingComponent id="activityRating" name="activityRating" emptyStarColor="#BBB" value={starRating}
                                     onStarClick={this.onRateChange.bind(this)}/>;

            postCommentContainer =

                    <form className="comment-form" method="POST" action="/postComment/">

                            <textarea placeholder="Skriv inn din kommentar her" id="commentInput"
                                      className="form-control custom-control"></textarea>
                            Send

                    </form>

;
        }

        if (this.state.noComments) {

        }

        if (this.state.show && !this.state.fetchedComments) {
            this.fetchComments()
        }

        let allComments = this.state.comments;

        return (
            <Modal
                show={this.state.show}
                onHide={this.closeActivityModal}
                bsSize="large"
                aria-labelledby="contained-modal-title-lg">
                <Modal.Header closeButton>
                    <Modal.Title id="contained-modal-title-lg">
                        <CalendarDateBox date={new Date(date)}/>

{activityName}

Arrangeres av: {provider} {ratingContainer}

                    </Modal.Title>
                </Modal.Header>
                <Modal.Body>

                        Dette arrangementet er tilpasset for: {suitedForContainer.label}

<Glyphicon glyph="glyphicon glyphicon-user"/>
                                Alder: {age}

<Glyphicon glyph="glyphicon glyphicon-time"/>
                                Tid: {time_start} - {time_end}

                                <Glyphicon glyph="glyphicon glyphicon-map-marker"/>
                                Sted: {location}

                                Vis på kart

                        {attendingContainer}

Om arrangementet

{description}

                    {/* videoContainer */}
                    {imageContainer}

Kommentarer

                    {postCommentContainer}

                        {allComments.map((com, i) =>

 {com.fields.userProfile_name} - {com.fields.date}

{com.fields.comment}

                        )}

                </Modal.Body>
                <Modal.Footer>
                    <Button onClick={this.editActivity}>Endre aktivitet</Button>
                    <Button onClick={this.closeActivityModal}>Lukk</Button>
                </Modal.Footer>
            </Modal>
        )
    }
}

export default ActivityModal;
<|endoftext|>


<commit_before>var suich = require('..'),
	assert = require('assert'),
	Q = require('q');

describe('safe url input checker library for node.js ', function() {

	describe('getIPAddresses() method', function() {
		it('#1 should get IP addresses', function(done) {
			suich.getIPAddresses('w3.org', function(err, res) {
				assert.equal(res, "128.30.52.45");
				done();
			});
		});
		it('#2 should get IP addresses', function(done) {
			suich.getIPAddresses('localhost', function(err, res) {
				assert.equal(res, "127.0.0.1");
				done();
			});
		});
		it('#3 should not get IP addresses', function(done) {
			suich.getIPAddresses('foofoofoo.com', function(err, res) {
				assert.ifError(res);
				done();
			});
		});
	});

	describe('isHostBlacklisted() method', function() {
		it('#1 should return true', function() {
			suich.isHostBlacklisted('google.com', ['google.com', 'w3.org'], function(err, res) {
				assert.equal(res, true);
			});
		});
		it('#2 should return false', function() {
			suich.isHostBlacklisted('foofoofoo.com', ['google.com', 'w3.org'], function(err, res) {
				assert.equal(res, false);
			});
		});
		it('#3 should return false', function() {
			suich.isHostBlacklisted('foofoofoo.com', [], function(err, res) {
				assert.equal(res, false);
			});
		});
		it('#4 should return true', function() {
			assert.equal(suich.isHostBlacklisted('google.com', ['google.com', 'w3.org']), true);
		});
		it('#5 should return false', function() {
			assert.equal(suich.isHostBlacklisted('foofoofoo.com', ['google.com', 'w3.org']), false);
		});
		it('#6 should return false', function() {
			assert.equal(suich.isHostBlacklisted('foofoofoo.com', []), false);
		});
	});

});<commit_msg>add isAddressLocal tests (mocha)<commit_after>var suich = require('..'),
	assert = require('assert'),
	Q = require('q');

describe('safe url input checker library for node.js ', function() {

	describe('getIPAddresses() method', function() {
		it('#1 should get IP addresses', function(done) {
			suich.getIPAddresses('w3.org', function(err, res) {
				assert.equal(res, "128.30.52.45");
				done();
			});
		});
		it('#2 should get IP addresses', function(done) {
			suich.getIPAddresses('localhost', function(err, res) {
				assert.equal(res, "127.0.0.1");
				done();
			});
		});
		it('#3 should not get IP addresses', function(done) {
			suich.getIPAddresses('foofoofoo.com', function(err, res) {
				assert.ifError(res);
				done();
			});
		});
	});

	describe('isHostBlacklisted() method', function() {
		it('#1 should return true', function() {
			suich.isHostBlacklisted('google.com', ['google.com', 'w3.org'], function(err, res) {
				assert.equal(res, true);
			});
		});
		it('#2 should return false', function() {
			suich.isHostBlacklisted('foofoofoo.com', ['google.com', 'w3.org'], function(err, res) {
				assert.equal(res, false);
			});
		});
		it('#3 should return false', function() {
			suich.isHostBlacklisted('foofoofoo.com', [], function(err, res) {
				assert.equal(res, false);
			});
		});
		it('#4 should return true', function() {
			assert.equal(suich.isHostBlacklisted('google.com', ['google.com', 'w3.org']), true);
		});
		it('#5 should return false', function() {
			assert.equal(suich.isHostBlacklisted('foofoofoo.com', ['google.com', 'w3.org']), false);
		});
		it('#6 should return false', function() {
			assert.equal(suich.isHostBlacklisted('foofoofoo.com', []), false);
		});
	});

	describe('isAddressLocal() method', function() {
		it('should return true', function() {
			suich.isAddressLocal('127.0.0.1', function(err, res) {
				assert.equal(res, true);
			});
		});
		it('should return false', function() {
			suich.isAddressLocal('128.30.52.45', function(err, res) {
				assert.equal(res, false);
			});
		});
		it('should return true', function() {
			assert.equal(suich.isAddressLocal('127.0.0.1'), true);
		});
		it('should return false', function() {
			assert.equal(suich.isAddressLocal('128.30.52.45'), false);
		});
	});

});<|endoftext|>


<commit_before>(function($) {

  // Matches trailing non-space characters.
  var chop = /(\s*\S+|\s)$/;

  // Return a truncated html string.  Delegates to $.fn.truncate.
  $.truncate = function(html, options) {
    return $('

').append(html).truncate(options).html();
  };

  // Truncate the contents of an element in place.
  $.fn.truncate = function(options) {
    if ($.isNumeric(options)) options = {length: options};
    var o = $.extend({}, $.truncate.defaults, options);

    return this.each(function() {
      var self = $(this);

      if (o.noBreaks) self.find('br').replaceWith(' ');

      var text = self.text();
      var excess = text.length - o.length;

      if (o.stripTags) self.text(text);

      // Chop off any partial words if appropriate.
      if (o.words && excess > 0) {
        excess = text.length - text.slice(0, o.length).replace(chop, '').length - 1;
      }

      if (excess < 0 || !excess && !o.truncated) return;

      // Iterate over each child node in reverse, removing excess text.
      $.each(self.contents().get().reverse(), function(i, el) {
        var $el = $(el);
        var text = $el.text();
        var length = text.length;

        // If the text is longer than the excess, remove the node and continue.
        if (length <= excess) {
          o.truncated = true;
          excess -= length;
          $el.remove();
          return;
        }

        // Replace text nodes with the appropriate substring and ellipsis.
        if (el.nodeType === 3) {
          text = text.slice(0, length - excess - 1);
          $el.replaceWith([document.createTextNode(text), o.ellipsis]);
          return false;
        }

        // Recursively truncate child nodes.
        $el.truncate($.extend(o, {length: length - excess}));
        return false;
      });
    });
  };

  $.truncate.defaults = {

    // Strip all html elements, leaving only plain text.
    stripTags: false,

    // Only truncate at word boundaries.
    words: false,

    // Replace instances of
 with a single space.
    noBreaks: false,

    // The maximum length of the truncated html.
    length: Infinity,

    // The character to use as the ellipsis.  The word joiner (U+2060) can be
    // used to prevent a hanging ellipsis, but displays incorrectly in Chrome
    // on Windows 7.
    // http://code.google.com/p/chromium/issues/detail?id=68323
    ellipsis: '\u2026' // '\u2060\u2026'

  };

})(jQuery);
<commit_msg>Use splitText.<commit_after>(function($) {

  // Matches trailing non-space characters.
  var chop = /(\s*\S+|\s)$/;

  // Return a truncated html string.  Delegates to $.fn.truncate.
  $.truncate = function(html, options) {
    return $('

').append(html).truncate(options).html();
  };

  // Truncate the contents of an element in place.
  $.fn.truncate = function(options) {
    if ($.isNumeric(options)) options = {length: options};
    var o = $.extend({}, $.truncate.defaults, options);

    return this.each(function() {
      var self = $(this);

      if (o.noBreaks) self.find('br').replaceWith(' ');

      var text = self.text();
      var excess = text.length - o.length;

      if (o.stripTags) self.text(text);

      // Chop off any partial words if appropriate.
      if (o.words && excess > 0) {
        excess = text.length - text.slice(0, o.length).replace(chop, '').length - 1;
      }

      if (excess < 0 || !excess && !o.truncated) return;

      // Iterate over each child node in reverse, removing excess text.
      $.each(self.contents().get().reverse(), function(i, el) {
        var $el = $(el);
        var text = $el.text();
        var length = text.length;

        // If the text is longer than the excess, remove the node and continue.
        if (length <= excess) {
          o.truncated = true;
          excess -= length;
          $el.remove();
          return;
        }

        // Remove the excess text and append the ellipsis.
        if (el.nodeType === 3) {
          $(el.splitText(length - excess - 1)).replaceWith(o.ellipsis);
          return false;
        }

        // Recursively truncate child nodes.
        $el.truncate($.extend(o, {length: length - excess}));
        return false;
      });
    });
  };

  $.truncate.defaults = {

    // Strip all html elements, leaving only plain text.
    stripTags: false,

    // Only truncate at word boundaries.
    words: false,

    // Replace instances of
 with a single space.
    noBreaks: false,

    // The maximum length of the truncated html.
    length: Infinity,

    // The character to use as the ellipsis.  The word joiner (U+2060) can be
    // used to prevent a hanging ellipsis, but displays incorrectly in Chrome
    // on Windows 7.
    // http://code.google.com/p/chromium/issues/detail?id=68323
    ellipsis: '\u2026' // '\u2060\u2026'

  };

})(jQuery);
<|endoftext|>


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

/**
 * Lightweight event & listener abstraction for a single event type.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const axon = require( 'AXON/axon' );

  // constants
  const shuffleListeners = _.hasIn( window, 'phet.chipper.queryParameters' ) && phet.chipper.queryParameters.shuffleListeners;

  class TinyEmitter {
    constructor() {

      // @private {function[]} - the listeners that will be called on emit
      this.listeners = [];

      // @private {function[][]} - during emit() keep track of which listeners should receive events in order to manage
      //                         - removal of listeners during emit()
      this.activeListenersStack = [];

      // for production memory concerns; no need to keep this around.
      if ( assert ) {

        // @private {boolean} - to keep track if it has been disposed or not
        this.isDisposed = false;
      }
    }

    /**
     * Disposes an Emitter. All listeners are removed.
     * @public
     */
    dispose() {
      this.listeners.length = 0; // See https://github.com/phetsims/axon/issues/124

      if ( assert ) {
        this.isDisposed = true;
      }
    }

    /**
     * Notify listeners
     * @public
     */
    emit() {
      assert && assert( !this.isDisposed, 'should not be called if disposed' );

      // Support for a query parameter that shuffles listeners, but bury behind assert so it will be stripped out on build
      // so it won't impact production performance.
      if ( assert && shuffleListeners ) {
        this.listeners = _.shuffle( this.listeners );
      }

      // Notify wired-up listeners, if any
      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );

        // Notify listeners--note the activeListenersStack could change as listeners are called, so we do this by index
        const lastEntry = this.activeListenersStack.length - 1;
        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ].apply( null, arguments );
        }

        this.activeListenersStack.pop();
      }
    }

    /**
     * Adds a listener which will be called during emit.
     * @param {function} listener
     * @public
     */
    addListener( listener ) {

      assert && assert( this.listeners.indexOf( listener ) === -1, 'Cannot add the same listener twice' );

      // If a listener is added during an emit(), we must make a copy of the current list of listeners--the newly added
      // listener will be available for the next emit() but not the one in progress.  This is to match behavior with
      // removeListener.
      this.defendListeners();

      this.listeners.push( listener );
    }

    /**
     * Removes a listener
     * @param {function} listener
     * @public
     */
    removeListener( listener ) {

      const index = this.listeners.indexOf( listener );

      // Throw an error when removing a non-listener (except when the Emitter has already been disposed, see
      // https://github.com/phetsims/sun/issues/394#issuecomment-419998231
      if ( assert && !this.isDisposed ) {
        assert( index !== -1, 'tried to removeListener on something that wasn\'t a listener' );
      }

      // If an emit is in progress, make a copy of the current list of listeners--the removed listener will remain in
      // the list and be called for this emit call, see #72
      this.defendListeners();

      this.listeners.splice( index, 1 );
    }

    /**
     * Removes all the listeners
     * @public
     */
    removeAllListeners() {
      while ( this.listeners.length > 0 ) {
        this.removeListener( this.listeners[ 0 ] );
      }
    }

    /**
     * If addListener/removeListener is called while emit() is in progress, we must make a defensive copy of the array
     * of listeners before changing the array, and use it for the rest of the notifications until the emit call has
     * completed.
     * @private
     */
    defendListeners() {

      for ( let i = this.activeListenersStack.length - 1; i >= 0; i-- ) {

        // Once we meet a level that was already defended, we can stop, since all previous levels are also defended
        if ( this.activeListenersStack[ i ].defended ) {
          break;
        }
        else {
          const defendedListeners = this.listeners.slice();

          // Mark copies as 'defended' so that it will use the original listeners when emit started and not the modified
          // list.
          defendedListeners.defended = true;
          this.activeListenersStack[ i ] = defendedListeners;
        }
      }
    }

    /**
     * Checks whether a listener is registered with this Emitter
     * @param {function} listener
     * @returns {boolean}
     * @public
     */
    hasListener( listener ) {
      assert && assert( arguments.length === 1, 'Emitter.hasListener should be called with 1 argument' );
      return this.listeners.indexOf( listener ) >= 0;
    }

    /**
     * Returns true if there are any listeners.
     * @returns {boolean}
     * @public
     */
    hasListeners() {
      assert && assert( arguments.length === 0, 'Emitter.hasListeners should be called without arguments' );
      return this.listeners.length > 0;
    }

    /**
     * Returns the number of listeners.
     * @returns {number}
     * @public
     */
    getListenerCount() {
      return this.listeners.length;
    }
  }

  return axon.register( 'TinyEmitter', TinyEmitter );
} );<commit_msg>fix bad text lint rules by testing with code tokens and strings, https://github.com/phetsims/chipper/issues/791<commit_after>// Copyright 2015-2019, University of Colorado Boulder

/**
 * Lightweight event & listener abstraction for a single event type.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const axon = require( 'AXON/axon' );

  // constants
  const shuffleListeners = _.hasIn( window, 'phet.chipper.queryParameters' ) && phet.chipper.queryParameters.shuffleListeners;

  class TinyEmitter {
    constructor() {

      // @private {function[]} - the listeners that will be called on emit
      this.listeners = [];

      // @private {function[][]} - during emit() keep track of which listeners should receive events in order to manage
      //                         - removal of listeners during emit()
      this.activeListenersStack = [];

      // for production memory concerns; no need to keep this around.
      if ( assert ) {

        // @private {boolean} - to keep track if it has been disposed or not
        this.isDisposed = false;
      }
    }

    /**
     * Disposes an Emitter. All listeners are removed.
     * @public
     */
    dispose() {
      this.listeners.length = 0; // See https://github.com/phetsims/axon/issues/124

      if ( assert ) {
        this.isDisposed = true;
      }
    }

    /**
     * Notify listeners
     * @public
     */
    emit() {
      assert && assert( !this.isDisposed, 'should not be called if disposed' );

      // Support for a query parameter that shuffles listeners, but bury behind assert so it will be stripped out on build
      // so it won't impact production performance.
      if ( assert && shuffleListeners ) {
        this.listeners = _.shuffle( this.listeners ); // eslint-disable-line bad-sim-text
      }

      // Notify wired-up listeners, if any
      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );

        // Notify listeners--note the activeListenersStack could change as listeners are called, so we do this by index
        const lastEntry = this.activeListenersStack.length - 1;
        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ].apply( null, arguments );
        }

        this.activeListenersStack.pop();
      }
    }

    /**
     * Adds a listener which will be called during emit.
     * @param {function} listener
     * @public
     */
    addListener( listener ) {

      assert && assert( this.listeners.indexOf( listener ) === -1, 'Cannot add the same listener twice' );

      // If a listener is added during an emit(), we must make a copy of the current list of listeners--the newly added
      // listener will be available for the next emit() but not the one in progress.  This is to match behavior with
      // removeListener.
      this.defendListeners();

      this.listeners.push( listener );
    }

    /**
     * Removes a listener
     * @param {function} listener
     * @public
     */
    removeListener( listener ) {

      const index = this.listeners.indexOf( listener );

      // Throw an error when removing a non-listener (except when the Emitter has already been disposed, see
      // https://github.com/phetsims/sun/issues/394#issuecomment-419998231
      if ( assert && !this.isDisposed ) {
        assert( index !== -1, 'tried to removeListener on something that wasn\'t a listener' );
      }

      // If an emit is in progress, make a copy of the current list of listeners--the removed listener will remain in
      // the list and be called for this emit call, see #72
      this.defendListeners();

      this.listeners.splice( index, 1 );
    }

    /**
     * Removes all the listeners
     * @public
     */
    removeAllListeners() {
      while ( this.listeners.length > 0 ) {
        this.removeListener( this.listeners[ 0 ] );
      }
    }

    /**
     * If addListener/removeListener is called while emit() is in progress, we must make a defensive copy of the array
     * of listeners before changing the array, and use it for the rest of the notifications until the emit call has
     * completed.
     * @private
     */
    defendListeners() {

      for ( let i = this.activeListenersStack.length - 1; i >= 0; i-- ) {

        // Once we meet a level that was already defended, we can stop, since all previous levels are also defended
        if ( this.activeListenersStack[ i ].defended ) {
          break;
        }
        else {
          const defendedListeners = this.listeners.slice();

          // Mark copies as 'defended' so that it will use the original listeners when emit started and not the modified
          // list.
          defendedListeners.defended = true;
          this.activeListenersStack[ i ] = defendedListeners;
        }
      }
    }

    /**
     * Checks whether a listener is registered with this Emitter
     * @param {function} listener
     * @returns {boolean}
     * @public
     */
    hasListener( listener ) {
      assert && assert( arguments.length === 1, 'Emitter.hasListener should be called with 1 argument' );
      return this.listeners.indexOf( listener ) >= 0;
    }

    /**
     * Returns true if there are any listeners.
     * @returns {boolean}
     * @public
     */
    hasListeners() {
      assert && assert( arguments.length === 0, 'Emitter.hasListeners should be called without arguments' );
      return this.listeners.length > 0;
    }

    /**
     * Returns the number of listeners.
     * @returns {number}
     * @public
     */
    getListenerCount() {
      return this.listeners.length;
    }
  }

  return axon.register( 'TinyEmitter', TinyEmitter );
} );<|endoftext|>


<commit_before>var compose = require('ksf/utils/compose');

/**
Container qui positionne des enfants de taille fixe dans un ordre donné.
Peut-être utilisé de façon incrémental
*/
module.exports = compose(function(axis) {
	this._children = {};
	this._sizeProp = (axis === 'vertical' ? 'height' : 'width');
	this._positionProp = (axis === 'vertical' ? 'top' : 'left');
	this._firstChildKey = null;
	this._lastChildKey = null;
}, {
	content: function(config) {
		var sizeProp = this._sizeProp;
		var children = this._children = {};
		var offset = 0;
		var previous = null;
		config.forEach(function(child) {
			previous && (children[previous].next = child.key);
			var cmpSize = child.cmp[sizeProp]();
			children[child.key] = {
				previous: previous,
				next: null,
				key: child.key,
				cmp: child.cmp,
				size: cmpSize,
				offset: offset,
			};
			offset += cmpSize;
			previous = child.key;
		}, this);
		this._size = offset;

		this._firstChildKey = config[0].key;
		this._lastChildKey = config[config.length-1].key;
		this._layoutFrom(this._firstChildKey);

		// this._emit('size', this._size);

		return this;
	},
	add: function(key, cmp, beforeKey) {
		beforeKey = beforeKey || null;
		var sizeProp = this._sizeProp;

		var previousKey = beforeKey ? this._children[beforeKey].previous : this._lastChildKey;
		var previousChild = previousKey ? this._children[previousKey] : null;
		var offset = previousChild ? previousChild.offset + previousChild.size : 0;
		var cmpSize = cmp[sizeProp]();
		this._children[key] = {
			previous: previousKey,
			next: beforeKey,
			key: key,
			cmp: cmp,
			size: cmpSize,
			offset: offset,
		};
		previousKey && (this._children[previousKey].next = key);
		beforeKey && (this._children[beforeKey].previous = key);

		this._layoutFrom(key);
		if (beforeKey === this._firstChildKey) {
			this._firstChildKey = key;
		}
		if (beforeKey === null) {
			this._lastChildKey = key;
		}

		this._size = (this._size || 0) + cmpSize;
		// this._emit('size', this._size);
	},
	remove: function(key) {
		var child = this._children[key];

		var previousKey = child.previous;
		var previousChild = previousKey ? this._children[previousKey] : null;

		var nextKey = child.next;
		var nextChild = nextKey ? this._children[nextKey] : null;

		previousChild && (previousChild.next = nextKey);
		nextChild && (nextChild.previous = previousKey);

		this._layoutFrom(nextKey);
		if (previousKey === null) {
			this._firstChildKey = nextKey;
		}
		if (nextKey === null) {
			this._lastChildKey = previousKey;
		}

		delete this._children[key];

		this._size = this._size - child.size;
	},
	move: function(key, beforeKey) {
		// TODO: optimize
		var cmp = this._children[key].cmp;
		this.remove(key);
		this.add(key, cmp, beforeKey);
	},
	_layoutFrom: function(key) {
		var positionProp = this._positionProp;
		var children = this._children;
		while(key) {
			var child = children[key];
			var previousChild = children[child.previous];
			var offset = child.offset = previousChild ? previousChild.offset + previousChild.size : 0;
			child.cmp[positionProp](this._position + offset); // apply offset
			key = child.next;
		}
	},
	children: function() {
		return this._children;
	},
	position: function(position) {
		if (arguments.length) {
			this._position = position;
			this._layoutFrom(this._firstChildKey);
			return this;
		} else {
			return this._position;
		}
	},
	size: function() {
		return this._size;
	},
	// onSize: function(cb) {
	// 	return this._on('size', cb);
	// },
});
<commit_msg>handle cas where content is an empty array in layout/Pile<commit_after>var compose = require('ksf/utils/compose');

/**
Container qui positionne des enfants de taille fixe dans un ordre donné.
Peut-être utilisé de façon incrémental
*/
module.exports = compose(function(axis) {
	this._children = {};
	this._sizeProp = (axis === 'vertical' ? 'height' : 'width');
	this._positionProp = (axis === 'vertical' ? 'top' : 'left');
	this._firstChildKey = null;
	this._lastChildKey = null;
}, {
	content: function(config) {
		var sizeProp = this._sizeProp;
		var children = this._children = {};
		var offset = 0;
		var previous = null;
		config.forEach(function(child) {
			previous && (children[previous].next = child.key);
			var cmpSize = child.cmp[sizeProp]();
			children[child.key] = {
				previous: previous,
				next: null,
				key: child.key,
				cmp: child.cmp,
				size: cmpSize,
				offset: offset,
			};
			offset += cmpSize;
			previous = child.key;
		}, this);
		this._size = offset;

		this._firstChildKey = config[0] && config[0].key;
		this._lastChildKey = config[config.length-1] && config[config.length-1].key;
		this._layoutFrom(this._firstChildKey);

		// this._emit('size', this._size);

		return this;
	},
	add: function(key, cmp, beforeKey) {
		beforeKey = beforeKey || null;
		var sizeProp = this._sizeProp;

		var previousKey = beforeKey ? this._children[beforeKey].previous : this._lastChildKey;
		var previousChild = previousKey ? this._children[previousKey] : null;
		var offset = previousChild ? previousChild.offset + previousChild.size : 0;
		var cmpSize = cmp[sizeProp]();
		this._children[key] = {
			previous: previousKey,
			next: beforeKey,
			key: key,
			cmp: cmp,
			size: cmpSize,
			offset: offset,
		};
		previousKey && (this._children[previousKey].next = key);
		beforeKey && (this._children[beforeKey].previous = key);

		this._layoutFrom(key);
		if (beforeKey === this._firstChildKey) {
			this._firstChildKey = key;
		}
		if (beforeKey === null) {
			this._lastChildKey = key;
		}

		this._size = (this._size || 0) + cmpSize;
		// this._emit('size', this._size);
	},
	remove: function(key) {
		var child = this._children[key];

		var previousKey = child.previous;
		var previousChild = previousKey ? this._children[previousKey] : null;

		var nextKey = child.next;
		var nextChild = nextKey ? this._children[nextKey] : null;

		previousChild && (previousChild.next = nextKey);
		nextChild && (nextChild.previous = previousKey);

		this._layoutFrom(nextKey);
		if (previousKey === null) {
			this._firstChildKey = nextKey;
		}
		if (nextKey === null) {
			this._lastChildKey = previousKey;
		}

		delete this._children[key];

		this._size = this._size - child.size;
	},
	move: function(key, beforeKey) {
		// TODO: optimize
		var cmp = this._children[key].cmp;
		this.remove(key);
		this.add(key, cmp, beforeKey);
	},
	_layoutFrom: function(key) {
		var positionProp = this._positionProp;
		var children = this._children;
		while(key) {
			var child = children[key];
			var previousChild = children[child.previous];
			var offset = child.offset = previousChild ? previousChild.offset + previousChild.size : 0;
			child.cmp[positionProp](this._position + offset); // apply offset
			key = child.next;
		}
	},
	children: function() {
		return this._children;
	},
	position: function(position) {
		if (arguments.length) {
			this._position = position;
			this._layoutFrom(this._firstChildKey);
			return this;
		} else {
			return this._position;
		}
	},
	size: function() {
		return this._size;
	},
	// onSize: function(cb) {
	// 	return this._on('size', cb);
	// },
});
<|endoftext|>


<commit_before>const
  colors = require('colors'),
  _ = require('lodash'),
  Nest = require('./Nest')

function Omissions(root){
  if(!root || typeof root !== 'string'){
    throw new Error('Ommisions constructor requires a root property of type String')
  }
  this.root = root
  this.omissions = new Array(0)
  this.props = new Array(0)
  this.inspectProps = new Array(0)
  this.nests = []
}

Omissions.prototype.add = function(omission) {
  this.omissions.push(omission)
}

Omissions.prototype.addProp = function(prop) {
  this.props.push(prop)
}

Omissions.prototype.addInspectProp = function(prop) {
  this.inspectProps.push(prop)
}

Omissions.prototype.logOmissions = function () {
  let omString = ''
  _.forEach(this.omissions, (omission) => {
    omString += `   -- ${omission} \n`
  })
  console.log(
    `  ${String(this.root).red} \n${String(omString).yellow}`
  )
}

Omissions.prototype.addNest = function(prop) {
  this.nests.push(new Nest(prop))
}

module.exports = Omissions
<commit_msg>- adding inspectRoot prop to keep track of nested properties - adding returnKeyWithRoot method to return keys with their root for inspection - commenting out addNest method as I have yet to think of an implementation for nests<commit_after>const
  colors = require('colors'),
  _ = require('lodash'),
  Nest = require('./Nest')

function Omissions(root){
  if(!root || typeof root !== 'string'){
    throw new Error('Ommisions constructor requires a root property of type String')
  }
  this.root = root
  this.inspectRoot = null
  this.omissions = new Array(0)
  this.props = new Array(0)
  this.inspectProps = new Array(0)
  this.nests = []
}

Omissions.prototype.setInspectRoot = function(root) {
  this.inspectRoot = root
}

Omissions.prototype.add = function(omission) {
  this.omissions.push(omission)
}

Omissions.prototype.addProp = function(prop) {
  this.props.push(prop)
}

Omissions.prototype.addInspectProp = function(prop) {
  this.inspectProps.push(prop)
}

Omissions.prototype.logOmissions = function () {
  let omString = ''
  _.forEach(this.omissions, (omission) => {
    omString += `   -- ${omission} \n`
  })
  console.log(
    `  ${String(this.root).red} \n${String(omString).yellow}`
  )
}

Omissions.prototype.getKeyWithRoot = function(key){
  if(this.inspectRoot === ''){
    return key
  } else {
    return `${this.inspectRoot}.${key}`
  }
}

// Omissions.prototype.addNest = function(prop) {
//   this.nests.push(new Nest(prop))
// }

module.exports = Omissions
<|endoftext|>