<commit_before>
/****
 * @package Grapnel.js
 * A lightweight JavaScript library for adding action hooks in URL hashtags/anchors.
 * https://github.com/gregsabia/Grapnel-js
 *
 * @author Greg Sabia
 * @support http://gregsabia.com
 *
 * Event listeners: ['hashchange', 'hookfound']
*/

var Grapnel = function(hook){
    var self = this;
    // Anchoring
    this.anchor = false;
    // Hook (default: ":")
    this.hook = hook || ':';
    // Value (default: false)
    this.value = false;
    // Actions
    this.actions = [];
    // Listeners
    this.listeners = [];
    /**
     * Add an event listener
     *
     * @param {String} event
     * @param {Function} callback
    */
    this.on = function(event, handler){
        return this.listeners.push({ event : event, handler : handler });
    }
    // Fire a listener
    this._trigger = function(event, data){
        for(i in this.listeners){
            if(this.listeners[i].event == event){
                this.listeners[i].handler.call(this, data);
            }
        }

        return this;
    }
    // Get anchor
    this.getAnchor = function(){
        var url_anchor = document.location.toString();
        if(url_anchor.match('#')){
            return this.anchor = url_anchor.split('#')[1];
        }else{
            return false;
        }
    }
    // Change anchor
    this.setAnchor = function(anchor){
        var url_anchor = document.location.toString();
        var anchor = anchor || '!';
        // Append anchor to location
        document.location = url_anchor.split('#')[0] + '#' + anchor;

        return this;
    }
    // Reset anchor
    this.clearAnchor = function(){
        return this.setAnchor(false);
    }
    // Parse hook
    this.parseHook = function(a){
        var a = a || this.anchor;

        if(this.anchor && a.match(this.hook)){
            this.value = a.split(this.hook)[1];
            this.anchor = a.split(this.hook)[0];
            // Fire event
            this._trigger('hookfound', [this.value, this.anchor]);

            return this.anchor;
        }else{
            return a;
        }
    }
    /**
     * Add an action and handler
     *
     * @param {String} action name
     * @param {Function} callback
    */
    this.add = function(action, handler){
        this.actions.push({ action : action, handler : handler });
        return this._run();
    }
    // Initialize
    this.getAnchor();
    this.parseHook();
    // Run hook action
    this._run = function(){
        // We've matched an anchor action
        for(i in this.actions){
            if(this.actions[i].action == this.anchor) this.actions[i].handler.call(this, this.value, this.anchor);
        }

        return this;
    }
    // Default anchor change event
    this.on('hashchange', function(){
        this.getAnchor();
        this.parseHook();
        this._run();
    });
    // Check current hash change event
    if(typeof window.onhashchange == 'function') this.on('hashchange', window.onhashchange);
    /**
     * Hash change event
     * TODO: increase browser compatibility. "window.onhashchange" can be supplemented in older browsers with setInterval()
    */
    window.onhashchange = function(){
        self._trigger('hashchange', self.anchor);
    }

    return this;
}
<commit_msg>Less ambiguous event triggering<commit_after>
/****
 * @package Grapnel.js
 * A lightweight JavaScript library for adding action hooks in URL hashtags/anchors.
 * https://github.com/gregsabia/Grapnel-js
 *
 * @author Greg Sabia
 * @support http://gregsabia.com
 *
 * Event listeners: ['ready', 'match', 'hashchange']
*/

var Grapnel = function(hook){
    var self = this;
    this.action = null;
    // Hook (default: ":")
    this.hook = hook || ':';
    // Value (default: null)
    this.value = null;
    // Actions
    this.actions = [];
    // Listeners
    this.listeners = [];
    /**
     * Add an event listener
     *
     * @param {String|Array} event
     * @param {Function} callback
    */
    this.on = function(event, handler){
        var events = (typeof event == 'string') ? event.split() : event;
        // Add listeners
        events.map(function(event){
            self.listeners.push({ event : event, handler : handler });
        });

        return this._run();
    }
    // Fire a listener
    this._trigger = function(event){
        var params = Array.prototype.slice.call(arguments, 1);

        this.listeners.map(function(listener){
            if(listener.event == event) listener.handler.apply(self, params);
        });

        return this;
    }
    // Get anchor
    this.getAnchor = function(){
        return (window.location.hash) ? window.location.hash.split('#')[1] : '';
    }
    // Change anchor
    this.setAnchor = function(anchor){
        window.location.hash = (!anchor) ? '' : anchor;
        return this;
    }
    // Reset anchor
    this.clearAnchor = function(){
        return this.setAnchor(false);
    }
    // Parse hook
    this.parse = function(){
        var action, value;

        if(this.getAnchor().match(this.hook)){
            // Found a hook!
            value = this.getAnchor().split(this.hook)[1];
            action = this.getAnchor().split(this.hook)[0];
        }

        return {
            value : value,
            action : action
        };
    }
    /**
     * Add an action and handler
     *
     * @param {String} action name
     * @param {Function} callback
    */
    this.add = function(name, handler){
        this.actions.push({ name : name, handler : handler });
        return this._run();
    }
    // Return matched actions
    this.matches = function(){
        var matches = [];

        this.actions.map(function(action){
            if(action.name == self.action) matches.push(action);
        });

        return matches;
    }
    // Run hook action
    this._run = function(){
        // Parse Hashtag in URL
        this.action = this.parse().action;
        this.value = this.parse().value;
        // If a match is found, trigger event
        if(this.matches().length > 0) this._trigger('match', this.value, this.action);
        // Run handlers matching current anchor
        this.matches().map(function(action){
            action.handler.call(self, self.value, self.action);
        });

        return this;
    }
    // Default anchor change event
    this.on(['ready', 'hashchange'], this._run);

    // Check current hash change event
    if(typeof window.onhashchange == 'function') this.on('hashchange', window.onhashchange);
    /**
     * Hash change event
     * TODO: increase browser compatibility. "window.onhashchange" can be supplemented in older browsers with setInterval()
    */
    window.onhashchange = function(){
        self._trigger('hashchange', self.getAnchor());
    }
    // Initialize
    this._trigger('ready', this);

    return this;
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name                YouTube - Mute Ads
// @name:de             YouTube - Mute Ads
// @version             1.0.4
// @description         Automatically mutes VideoAds
// @description:de      Schaltet Werbung auf YouTube automatisch stumm
// @autor               VVind0wM4ker
// @namespace           https://github.com/VVind0wM4ker/Userscripts
// @homepageURL         https://github.com/VVind0wM4ker/Userscripts/tree/master/YouTube_Mute_Ads
// @license             MIT License
// @grant               none
// @include             http*://*.youtube.com/watch*
// ==/UserScript==

var adPlaying = 0;
var interval = 500
var vid = document.getElementsByClassName("video-stream html5-main-video")[0];

var timer;

vid.onplay = function() {startTimer();};
vid.onpause = function() {pauseTimer();};

var muteAds = function () {

    //console.log("check for Ads")					//for testing

    if (document.getElementsByClassName("ad-interrupting").length > 0) {

        adPlaying = 1;
        vid.mute();
    }
    else {

        if (adPlaying == 1) {

            adPlaying = 0;
            vid.unMute();
        }
    }
};

function pauseTimer () {

    clearInterval(timer);
}

function startTimer () {

    clearInterval(timer);
    timer = setInterval(muteAds, interval);
}
<commit_msg>Delete YouTube_Mute_Ads.js<commit_after><|endoftext|>


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

/**
 * Highlight node for navigation bar screen buttons, phet button, etc.
 */
define( function( require ) {
  'use strict';

  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var inherit = require( 'PHET_CORE/inherit' );

  function HighlightNode( width, height, options ) {
    options = _.extend( {
      whiteHighlight: true
    }, options );

    var outerColor = options.whiteHighlight ? 'black' : 'white';
    var innerColor = options.whiteHighlight ? 'white' : 'black';
    var leftBar = new Path( Shape.lineSegment( 0, 0, 0, height ), {
      lineWidth: 1,
      stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, outerColor ).addColorStop( 0.5, innerColor ).addColorStop( 1, outerColor )
    } );
    var rightBar = new Path( Shape.lineSegment( width, 0, width, height ), {
      lineWidth: 1,
      stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, outerColor ).addColorStop( 0.5, innerColor ).addColorStop( 1, outerColor )
    } );

    options = _.extend( { children: [leftBar, rightBar] }, options );
    Node.call( this, options );
  }

  return inherit( Node, HighlightNode );
} );<commit_msg>made highlight non-pickable<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Highlight node for navigation bar screen buttons, phet button, etc.
 */
define( function( require ) {
  'use strict';

  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var inherit = require( 'PHET_CORE/inherit' );

  function HighlightNode( width, height, options ) {
    options = _.extend( {
      whiteHighlight: true
    }, options );

    var outerColor = options.whiteHighlight ? 'black' : 'white';
    var innerColor = options.whiteHighlight ? 'white' : 'black';
    var leftBar = new Path( Shape.lineSegment( 0, 0, 0, height ), {
      lineWidth: 1,
      stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, outerColor ).addColorStop( 0.5, innerColor ).addColorStop( 1, outerColor )
    } );
    var rightBar = new Path( Shape.lineSegment( width, 0, width, height ), {
      lineWidth: 1,
      stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, outerColor ).addColorStop( 0.5, innerColor ).addColorStop( 1, outerColor )
    } );

    options = _.extend( { children: [leftBar, rightBar], pickable: false}, options );
    Node.call( this, options );
  }

  return inherit( Node, HighlightNode );
} );<|endoftext|>


<commit_before>$(function(){
	var transforms=[
		{
			name:'Continuous-time Fourier transform (CTFT)', // angular frequency, non-unitary
			wikipedia:'http://en.wikipedia.org/wiki/Fourier_transform',
			timeDefinition:'x(t) = \\frac{1}{2\\pi} \\int\\limits_{-\\infty}^{+\\infty} X(j\\omega) e^{j\\omega t} \\,\\mathrm{d}\\omega',
			freqDefinition:'X(j\\omega) = \\int\\limits_{-\\infty}^{+\\infty} x(t) e^{-j\\omega t} \\,\\mathrm{d}t',
			timeDefinitionNote:'function \\(x(t)\\) of continuous variable \\(t\\)',
			freqDefinitionNote:'function \\(X(j\\omega)\\) of continuous variable \\(\\omega\\)',
			conjinvTimeFormula:function(neg,inv,conj){
				return (neg?'-':'')+'x'+(conj?'^*':'')+'('+(inv?'-':'')+'t)';
			},
			conjinvFreqFormula:function(neg,inv,conj){
				return (neg?'-':'')+'X'+(conj?'^*':'')+'('+(inv?'-':'')+'j\\omega)';
			},
			modshiftTimeFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega_0 t}':'')+'x(t'+(shift?(shift<0?'-':'+')+'t_0':'')+')';
			},
			modshiftFreqFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega t_0}':'')+'X(j'+(shift?'(\\omega'+(shift<0?'-':'+')+'\\omega_0)':'\\omega')+')';
			},
			intdiffFormulas:[
				'x(t)'       ,'\\frac{d}{dt} x(t)',
				'X(j\\omega)','j\\omega X(j\\omega)',
				'-j t x(t)',
				'\\frac{d}{d\\omega} X(j\\omega)'
			]
		},{
			name:'Discrete-time Fourier transform (DTFT)',
			wikipedia:'http://en.wikipedia.org/wiki/Discrete-time_Fourier_transform',
			timeDefinition:'x[n] = \\frac{1}{2\\pi} \\int\\limits_{\\langle 2\\pi \\rangle} X(e^{j\\omega}) e^{j\\omega n} \\,\\mathrm{d}\\omega',
			freqDefinition:'X(e^{j\\omega}) = \\sum_{n=-\\infty}^{+\\infty} x[n] e^{-j\\omega n}',
			timeDefinitionNote:'function \\(x[n]\\) of discrete variable \\(n\\)',
			freqDefinitionNote:'periodic function \\(X(e^{j\\omega})\\) of continuous variable \\(\\omega\\)
 with period \\(2\\pi\\)',
			conjinvTimeFormula:function(neg,inv,conj){
				return (neg?'-':'')+'x'+(conj?'^*':'')+'['+(inv?'-':'')+'n]';
			},
			conjinvFreqFormula:function(neg,inv,conj){
				return (neg?'-':'')+'X'+(conj?'^*':'')+'(e^{'+(inv?'-':'')+'j\\omega})';
			},
			modshiftTimeFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega_0 n}':'')+'x[n'+(shift?(shift<0?'-':'+')+'n_0':'')+']';
			},
			modshiftFreqFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega n_0}':'')+'X(e^{j'+(shift?'(\\omega'+(shift<0?'-':'+')+'\\omega_0)':'\\omega')+'})';
			},
			intdiffFormulas:[
				'x[n]'           ,'x[n]-x[n-1]',
				'X(e^{j\\omega})','(1-e^{-j\\omega}) X(e^{j\\omega})',
				'-j n x[n]',
				'\\frac{d}{d\\omega} X(e^{j\\omega})'
			]
		},{
			name:'Discrete Fourier transform (DFT)',
			wikipedia:'http://en.wikipedia.org/wiki/Discrete_Fourier_transform',
			timeDefinition:'x[n] = \\frac 1 N \\sum_{k=0}^{N-1} X[k] W_N^{-kn}',
			freqDefinition:'X[k] = \\sum_{n=0}^{N-1} x[n] W_N^{kn}',
			timeDefinitionNote:'function \\(x[n]\\) of discrete variable \\(n\\)
 with support \\([0;N-1]\\);
 variable is interpreted modulo \\(N\\);
 \\(W_N = e^{-j \\frac{2\\pi}{N}}\\)',
			freqDefinitionNote:'function \\(X[k]\\) of discrete variable \\(k\\)
 with support \\([0;N-1]\\);
 variable is interpreted modulo \\(N\\);
 \\(W_N = e^{-j \\frac{2\\pi}{N}}\\)',
			conjinvTimeFormula:function(neg,inv,conj){
				return (neg?'-':'')+'x'+(conj?'^*':'')+'['+(inv?'-':'')+'n]';
			},
			conjinvFreqFormula:function(neg,inv,conj){
				return (neg?'-':'')+'X'+(conj?'^*':'')+'['+(inv?'-':'')+'k]';
			},
			modshiftTimeFormula:function(mod,shift){
				return (mod?'W_N^{'+(mod>0?'-':'')+'k_0 n}':'')+'x[n'+(shift?(shift<0?'-':'+')+'n_0':'')+']';
			},
			modshiftFreqFormula:function(mod,shift){
				return (mod?'W_N^{'+(mod>0?'-':'')+'k n_0}':'')+'X[k'+(shift?(shift<0?'-':'+')+'k_0':'')+']';
			},
			intdiffFormulas:[
				'x[n]','x[n]-x[n-1]',
				'X[k]','(1-W_N^k) X[k]',
				'(1-W_N^{-n}) x[n]',
				'X[n]-X[n-1]'
			]
		}
	];
	var conjinvTimePatterns=[
		[1,1,1],[1,1,0],
		[1,0,1],[0,0,0],[0,0,1],
		        [0,1,0],[0,1,1]
	];
	var conjinvFreqPatterns=[
		[1,0,1],[1,1,0],
		[1,1,1],[0,0,0],[0,1,1],
		        [0,1,0],[0,0,1]
	];
	var modshiftTimePatterns=[
		        [ 0,+1],
		[-1, 0],[ 0, 0],[+1, 0],
		        [ 0,-1]
	];
	var modshiftFreqPatterns=[
		        [+1, 0],
		[ 0,+1],[ 0, 0],[ 0,-1],
		        [-1, 0]
	];
	$('.signal-transform-properties').each(function(){
		var tableElm=$(this);
		var transformElm=tableElm.find('caption');
		var transformSelectElm=null;
		transformElm.wrapInner("");
		var transformDropdownElm=transformElm.find('.signal-transform-dropdown');
		transformDropdownElm.click(function(){
			if (transformSelectElm===null) {
				transformSelectElm=$("
");
				transforms.forEach(function(transform){
					transformSelectElm.append(
						$("
"+transform.name+"
").click(function(){
							transformSelectElm.remove();
							transformSelectElm=null;
							transformDropdownElm.html(transform.name+"[W]");
							tableElm.find('.signal-transform-properties-definition td.time .formula .item').text('$$'+transform.timeDefinition+'$$');
							tableElm.find('.signal-transform-properties-definition td.freq .formula .item').text('$$'+transform.freqDefinition+'$$');
							tableElm.find('.signal-transform-properties-definition td.time .formula .note.at-b').html(transform.timeDefinitionNote);
							tableElm.find('.signal-transform-properties-definition td.freq .formula .note.at-b').html(transform.freqDefinitionNote);
							tableElm.find('.signal-transform-properties-conjinv td.time .formula .item').text(function(i){
								return '$$'+transform.conjinvTimeFormula.apply(null,conjinvTimePatterns[i])+'$$';
							});
							tableElm.find('.signal-transform-properties-conjinv td.freq .formula .item').text(function(i){
								return '$$'+transform.conjinvFreqFormula.apply(null,conjinvFreqPatterns[i])+'$$';
							});
							tableElm.find('.signal-transform-properties-modshift td.time .formula .item').text(function(i){
								return '$$'+transform.modshiftTimeFormula.apply(null,modshiftTimePatterns[i])+'$$';
							});
							tableElm.find('.signal-transform-properties-modshift td.freq .formula .item').text(function(i){
								return '$$'+transform.modshiftFreqFormula.apply(null,modshiftFreqPatterns[i])+'$$';
							});
							// simpler code without pretending that we're making a CAS
							tableElm.find('.signal-transform-properties-intdiff td .formula .item').text(function(i){
								return '$$'+transform.intdiffFormulas[i]+'$$';
							});
							MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
						})
					);
				});
				transformElm.append(transformSelectElm);
			} else {
				transformSelectElm.remove();
				transformSelectElm=null;
			}
		});
                tableElm.find('tr').each(function(){
			$(this).find('td.time .formula').each(function(i){
				var timeFormulaElm=$(this);
				var freqFormulaElm=timeFormulaElm.parents('tr').find('td.freq .formula').eq(i); // TODO probably requires 'parents'
				timeFormulaElm.add(freqFormulaElm).hover(function(){
					timeFormulaElm.addClass('active');
					freqFormulaElm.addClass('active');
					var tOffset=timeFormulaElm.offset();
					var fOffset=freqFormulaElm.offset();
					var tWidth=timeFormulaElm.width();
					var tHeight=timeFormulaElm.height();
					var line=$("

")
						.appendTo(timeFormulaElm)
						.offset({top:tOffset.top+tHeight/2-2,left:tOffset.left+tWidth})
						.width(fOffset.left-tOffset.left-tWidth)
					;
				},function(){
					timeFormulaElm.removeClass('active').find('.line').remove();
					freqFormulaElm.removeClass('active');
				});
			});
		});
		tableElm.find('tbody').each(function(){
			var tbodyElm=$(this);
			tbodyElm.find('tr:first-child th').click(function(ev){
				tbodyElm.removeClass('hidden');
				//ev.stopPropagation();
			});
			$("
").append($("
• • •
").click(function(ev){
				tbodyElm.addClass('hidden');
				//ev.stopPropagation();
			})).appendTo(tbodyElm.find('td.both').eq(0));
			var timeLinkElms=tableElm.find('td.time .link');
			var freqLinkElms=tableElm.find('td.freq .link');
			timeLinkElms.each(function(i){
				var timeLinkElm=timeLinkElms.eq(i);
				var freqLinkElm=freqLinkElms.eq(i);
                                var correctedOneLine=false;
				timeLinkElm.add(freqLinkElm).hover(function(){
					// FIXME it runs three times, maybe because of weird ::before use
					timeLinkElm.addClass('active');
					freqLinkElm.addClass('active');
					// single-line vs multiple-line note detection
					function correctOneLine() {
						var elm=$(this);
						if (!elm.hasClass('at-l') && !elm.hasClass('at-r')) return;
						var t=$("|").prependTo(elm); // span height will be 0 on chrome unless non-whitespace character is inserted
						var h1=t.height();
						t.remove();
						elm.wrapInner("");
						var h2=elm.children('span').height();
						if (h1>=h2) {
							elm.addClass('one-line');
						}
					}
					if (!correctedOneLine) {
						timeLinkElm.find('.note').each(correctOneLine);
						freqLinkElm.find('.note').each(correctOneLine);
						correctedOneLine=true;
					}
				},function(){
					timeLinkElm.removeClass('active');
					freqLinkElm.removeClass('active');
				});
			});
		});
		// $$\overset{\mathcal F}{\rightarrow}$$
	});
});
<commit_msg>fix: don't hook multiple hover handlers in
<commit_after>$(function(){
	var transforms=[
		{
			name:'Continuous-time Fourier transform (CTFT)', // angular frequency, non-unitary
			wikipedia:'http://en.wikipedia.org/wiki/Fourier_transform',
			timeDefinition:'x(t) = \\frac{1}{2\\pi} \\int\\limits_{-\\infty}^{+\\infty} X(j\\omega) e^{j\\omega t} \\,\\mathrm{d}\\omega',
			freqDefinition:'X(j\\omega) = \\int\\limits_{-\\infty}^{+\\infty} x(t) e^{-j\\omega t} \\,\\mathrm{d}t',
			timeDefinitionNote:'function \\(x(t)\\) of continuous variable \\(t\\)',
			freqDefinitionNote:'function \\(X(j\\omega)\\) of continuous variable \\(\\omega\\)',
			conjinvTimeFormula:function(neg,inv,conj){
				return (neg?'-':'')+'x'+(conj?'^*':'')+'('+(inv?'-':'')+'t)';
			},
			conjinvFreqFormula:function(neg,inv,conj){
				return (neg?'-':'')+'X'+(conj?'^*':'')+'('+(inv?'-':'')+'j\\omega)';
			},
			modshiftTimeFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega_0 t}':'')+'x(t'+(shift?(shift<0?'-':'+')+'t_0':'')+')';
			},
			modshiftFreqFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega t_0}':'')+'X(j'+(shift?'(\\omega'+(shift<0?'-':'+')+'\\omega_0)':'\\omega')+')';
			},
			intdiffFormulas:[
				'x(t)'       ,'\\frac{d}{dt} x(t)',
				'X(j\\omega)','j\\omega X(j\\omega)',
				'-j t x(t)',
				'\\frac{d}{d\\omega} X(j\\omega)'
			]
		},{
			name:'Discrete-time Fourier transform (DTFT)',
			wikipedia:'http://en.wikipedia.org/wiki/Discrete-time_Fourier_transform',
			timeDefinition:'x[n] = \\frac{1}{2\\pi} \\int\\limits_{\\langle 2\\pi \\rangle} X(e^{j\\omega}) e^{j\\omega n} \\,\\mathrm{d}\\omega',
			freqDefinition:'X(e^{j\\omega}) = \\sum_{n=-\\infty}^{+\\infty} x[n] e^{-j\\omega n}',
			timeDefinitionNote:'function \\(x[n]\\) of discrete variable \\(n\\)',
			freqDefinitionNote:'periodic function \\(X(e^{j\\omega})\\) of continuous variable \\(\\omega\\)
 with period \\(2\\pi\\)',
			conjinvTimeFormula:function(neg,inv,conj){
				return (neg?'-':'')+'x'+(conj?'^*':'')+'['+(inv?'-':'')+'n]';
			},
			conjinvFreqFormula:function(neg,inv,conj){
				return (neg?'-':'')+'X'+(conj?'^*':'')+'(e^{'+(inv?'-':'')+'j\\omega})';
			},
			modshiftTimeFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega_0 n}':'')+'x[n'+(shift?(shift<0?'-':'+')+'n_0':'')+']';
			},
			modshiftFreqFormula:function(mod,shift){
				return (mod?'e^{'+(mod<0?'-':'')+'j\\omega n_0}':'')+'X(e^{j'+(shift?'(\\omega'+(shift<0?'-':'+')+'\\omega_0)':'\\omega')+'})';
			},
			intdiffFormulas:[
				'x[n]'           ,'x[n]-x[n-1]',
				'X(e^{j\\omega})','(1-e^{-j\\omega}) X(e^{j\\omega})',
				'-j n x[n]',
				'\\frac{d}{d\\omega} X(e^{j\\omega})'
			]
		},{
			name:'Discrete Fourier transform (DFT)',
			wikipedia:'http://en.wikipedia.org/wiki/Discrete_Fourier_transform',
			timeDefinition:'x[n] = \\frac 1 N \\sum_{k=0}^{N-1} X[k] W_N^{-kn}',
			freqDefinition:'X[k] = \\sum_{n=0}^{N-1} x[n] W_N^{kn}',
			timeDefinitionNote:'function \\(x[n]\\) of discrete variable \\(n\\)
 with support \\([0;N-1]\\);
 variable is interpreted modulo \\(N\\);
 \\(W_N = e^{-j \\frac{2\\pi}{N}}\\)',
			freqDefinitionNote:'function \\(X[k]\\) of discrete variable \\(k\\)
 with support \\([0;N-1]\\);
 variable is interpreted modulo \\(N\\);
 \\(W_N = e^{-j \\frac{2\\pi}{N}}\\)',
			conjinvTimeFormula:function(neg,inv,conj){
				return (neg?'-':'')+'x'+(conj?'^*':'')+'['+(inv?'-':'')+'n]';
			},
			conjinvFreqFormula:function(neg,inv,conj){
				return (neg?'-':'')+'X'+(conj?'^*':'')+'['+(inv?'-':'')+'k]';
			},
			modshiftTimeFormula:function(mod,shift){
				return (mod?'W_N^{'+(mod>0?'-':'')+'k_0 n}':'')+'x[n'+(shift?(shift<0?'-':'+')+'n_0':'')+']';
			},
			modshiftFreqFormula:function(mod,shift){
				return (mod?'W_N^{'+(mod>0?'-':'')+'k n_0}':'')+'X[k'+(shift?(shift<0?'-':'+')+'k_0':'')+']';
			},
			intdiffFormulas:[
				'x[n]','x[n]-x[n-1]',
				'X[k]','(1-W_N^k) X[k]',
				'(1-W_N^{-n}) x[n]',
				'X[n]-X[n-1]'
			]
		}
	];
	var conjinvTimePatterns=[
		[1,1,1],[1,1,0],
		[1,0,1],[0,0,0],[0,0,1],
		        [0,1,0],[0,1,1]
	];
	var conjinvFreqPatterns=[
		[1,0,1],[1,1,0],
		[1,1,1],[0,0,0],[0,1,1],
		        [0,1,0],[0,0,1]
	];
	var modshiftTimePatterns=[
		        [ 0,+1],
		[-1, 0],[ 0, 0],[+1, 0],
		        [ 0,-1]
	];
	var modshiftFreqPatterns=[
		        [+1, 0],
		[ 0,+1],[ 0, 0],[ 0,-1],
		        [-1, 0]
	];
	$('.signal-transform-properties').each(function(){
		var tableElm=$(this);
		var transformElm=tableElm.find('caption');
		var transformSelectElm=null;
		transformElm.wrapInner("");
		var transformDropdownElm=transformElm.find('.signal-transform-dropdown');
		transformDropdownElm.click(function(){
			if (transformSelectElm===null) {
				transformSelectElm=$("
");
				transforms.forEach(function(transform){
					transformSelectElm.append(
						$("
"+transform.name+"
").click(function(){
							transformSelectElm.remove();
							transformSelectElm=null;
							transformDropdownElm.html(transform.name+"[W]");
							tableElm.find('.signal-transform-properties-definition td.time .formula .item').text('$$'+transform.timeDefinition+'$$');
							tableElm.find('.signal-transform-properties-definition td.freq .formula .item').text('$$'+transform.freqDefinition+'$$');
							tableElm.find('.signal-transform-properties-definition td.time .formula .note.at-b').html(transform.timeDefinitionNote);
							tableElm.find('.signal-transform-properties-definition td.freq .formula .note.at-b').html(transform.freqDefinitionNote);
							tableElm.find('.signal-transform-properties-conjinv td.time .formula .item').text(function(i){
								return '$$'+transform.conjinvTimeFormula.apply(null,conjinvTimePatterns[i])+'$$';
							});
							tableElm.find('.signal-transform-properties-conjinv td.freq .formula .item').text(function(i){
								return '$$'+transform.conjinvFreqFormula.apply(null,conjinvFreqPatterns[i])+'$$';
							});
							tableElm.find('.signal-transform-properties-modshift td.time .formula .item').text(function(i){
								return '$$'+transform.modshiftTimeFormula.apply(null,modshiftTimePatterns[i])+'$$';
							});
							tableElm.find('.signal-transform-properties-modshift td.freq .formula .item').text(function(i){
								return '$$'+transform.modshiftFreqFormula.apply(null,modshiftFreqPatterns[i])+'$$';
							});
							// simpler code without pretending that we're making a CAS
							tableElm.find('.signal-transform-properties-intdiff td .formula .item').text(function(i){
								return '$$'+transform.intdiffFormulas[i]+'$$';
							});
							MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
						})
					);
				});
				transformElm.append(transformSelectElm);
			} else {
				transformSelectElm.remove();
				transformSelectElm=null;
			}
		});
                tableElm.find('tr').each(function(){
			$(this).find('td.time .formula').each(function(i){
				var timeFormulaElm=$(this);
				var freqFormulaElm=timeFormulaElm.parents('tr').find('td.freq .formula').eq(i);
				timeFormulaElm.add(freqFormulaElm).hover(function(){
					timeFormulaElm.addClass('active');
					freqFormulaElm.addClass('active');
					var tOffset=timeFormulaElm.offset();
					var fOffset=freqFormulaElm.offset();
					var tWidth=timeFormulaElm.width();
					var tHeight=timeFormulaElm.height();
					var line=$("

")
						.appendTo(timeFormulaElm)
						.offset({top:tOffset.top+tHeight/2-2,left:tOffset.left+tWidth})
						.width(fOffset.left-tOffset.left-tWidth)
					;
				},function(){
					timeFormulaElm.removeClass('active').find('.line').remove();
					freqFormulaElm.removeClass('active');
				});
			});
		});
		tableElm.find('tbody').each(function(){
			var tbodyElm=$(this);
			tbodyElm.find('tr:first-child th').click(function(ev){
				tbodyElm.removeClass('hidden');
				//ev.stopPropagation();
			});
			$("
").append($("
• • •
").click(function(ev){
				tbodyElm.addClass('hidden');
				//ev.stopPropagation();
			})).appendTo(tbodyElm.find('td.both').eq(0));
			var timeLinkElms=tbodyElm.find('td.time .link');
			var freqLinkElms=tbodyElm.find('td.freq .link');
			timeLinkElms.each(function(i){
				var timeLinkElm=timeLinkElms.eq(i);
				var freqLinkElm=freqLinkElms.eq(i);
                                var correctedOneLine=false;
				timeLinkElm.add(freqLinkElm).hover(function(){
					timeLinkElm.addClass('active');
					freqLinkElm.addClass('active');
					// single-line vs multiple-line note detection
					function correctOneLine() {
						var elm=$(this);
						if (!elm.hasClass('at-l') && !elm.hasClass('at-r')) return;
						var t=$("|").prependTo(elm); // span height will be 0 on chrome unless non-whitespace character is inserted
						var h1=t.height();
						t.remove();
						elm.wrapInner("");
						var h2=elm.children('span').height();
						if (h1>=h2) {
							elm.addClass('one-line');
						}
					}
					if (!correctedOneLine) {
						timeLinkElm.find('.note').each(correctOneLine);
						freqLinkElm.find('.note').each(correctOneLine);
						correctedOneLine=true;
					}
				},function(){
					timeLinkElm.removeClass('active');
					freqLinkElm.removeClass('active');
				});
			});
		});
		// $$\overset{\mathcal F}{\rightarrow}$$
	});
});
<|endoftext|>


<commit_before>// ==UserScript==
// @name                YouTube - Mute Ads
// @name:de             YouTube - Mute Ads
// @version             1.0.2
// @description         Automatically mutes VideoAds
// @description:de      Schaltet Werbung auf YouTube automatisch stumm
// @autor               VVind0wM4ker
// @namespace           https://github.com/VVind0wM4ker/Userscripts
// @license             MIT License
// @grant               none
// @include             http*://*.youtube.com/watch*
// ==/UserScript==

var adPlaying = 0;
var interval = 500
var vid = document.getElementsByClassName("video-stream html5-main-video")[0];

var timer, adTimeout;

vid.onplay = function() {startTimer();};
vid.onpause = function() {pauseTimer();};

var muteAds = function () {

    //console.log("check for Ads")					//for testing

    if (document.getElementsByClassName("ad-showing").length > 0) {

        adPlaying = 1;
        vid.mute();
    }
    else {

        if (adPlaying == 1) {

            adPlaying = 0;
            vid.unMute();
        }
    }
};

function pauseTimer () {

    clearInterval(timer);
    clearTimeout(adTimeout);
}

function startTimer () {

    clearInterval(timer);
    clearTimeout(adTimeout);
    timer = setInterval(muteAds, interval);
}
<commit_msg>Update YouTube_Mute_Ads.js<commit_after>// ==UserScript==
// @name                YouTube - Mute Ads
// @name:de             YouTube - Mute Ads
// @version             1.0.2
// @description         Automatically mutes VideoAds
// @description:de      Schaltet Werbung auf YouTube automatisch stumm
// @autor               VVind0wM4ker
// @namespace           https://github.com/VVind0wM4ker/Userscripts
// @license             MIT License
// @grant               none
// @include             http*://*.youtube.com/watch*
// ==/UserScript==

var adPlaying = 0;
var interval = 500
var vid = document.getElementsByClassName("video-stream html5-main-video")[0];

var timer, adTimeout;

vid.onplay = function() {startTimer();};
vid.onpause = function() {pauseTimer();};

var muteAds = function () {

    //console.log("check for Ads")					//for testing

    if (document.getElementsByClassName("ad-interrupting").length > 0) {

        adPlaying = 1;
        vid.mute();
    }
    else {

        if (adPlaying == 1) {

            adPlaying = 0;
            vid.unMute();
        }
    }
};

function pauseTimer () {

    clearInterval(timer);
    clearTimeout(adTimeout);
}

function startTimer () {

    clearInterval(timer);
    clearTimeout(adTimeout);
    timer = setInterval(muteAds, interval);
}
<|endoftext|>


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

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the left and the PhET Logo and options menu at the right.
 * For a multi-screen sim, it shows icons for all of the other screens, with the screen name at the left and the PhET Logo and options menu at the right.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var HomeButton = require( 'JOIST/HomeButton' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var inherit = require( 'PHET_CORE/inherit' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );

  /**
   * Create a nav bar.  Layout assumes all of the screen widths are the same.
   * @param {Sim} sim
   * @param {Array<Screen>} screens
   * @param {PropertySet} model see joist.Sim
   * @param {Boolean} whiteColorScheme true if the color scheme should be white, false if it should be black
   * @constructor
   */
  function NavigationBar( sim, screens, model, whiteColorScheme ) {

    var thisNode = this;
    this.screens = screens;

    this.navBarHeight = 40;
    this.navBarScale = 1;
    this.navBarWidth = 768;

    //Renderer must be specified here because the node is added directly to the scene (instead of to some other node that already has svg renderer
    Node.call( this, {renderer: 'svg'} );
    this.background = new Rectangle( 0, 0, 0, 0, {fill: whiteColorScheme ? 'white' : 'black', pickable: false} );
    this.addChild( this.background );

    this.phetButton = new PhetButton( sim, whiteColorScheme, false );
    this.addChild( this.phetButton );

    this.titleLabel = new Text( sim.name, {font: new PhetFont( 18 ), fill: whiteColorScheme ? 'black' : 'white', pickable: false} );
    this.addChild( this.titleLabel );

    if ( screens.length > 1 ) {

      //Create buttons once so we can get their dimensions
      var buttons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton( sim, screen, thisNode.navBarHeight, whiteColorScheme, 0 );
      } );
      var maxWidth = Math.max( 50, _.max( buttons, function( button ) {return button.width;} ).width );

      //Create buttons again with equivalent sizes
      buttons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton( sim, screen, thisNode.navBarHeight, whiteColorScheme, maxWidth );
      } );

      this.buttonHBox = new HBox( {children: buttons, spacing: 4} );
      this.addChild( this.buttonHBox );

      //add the home icon
      this.homeIcon = new HomeButton( whiteColorScheme ? '#222' : 'white', whiteColorScheme ? '#444' : 'gray', whiteColorScheme );
      this.homeIcon.addListener( function() {model.showHomeScreen = true;} );
      this.homeIcon.addPeer( '<input type="button" aria-label="Home Screen">', {click: function() {model.showHomeScreen = true;}, tabIndex: 100} );
      this.addChild( this.homeIcon );
    }
  }

  return inherit( Node, NavigationBar, {
    relayout: function() {
      var navigationBar = this;
      navigationBar.background.rectHeight = this.navBarHeight;
      navigationBar.background.rectWidth = this.navBarWidth;

      if ( this.buttonHBox ) {
        this.buttonHBox.setScaleMagnitude( navigationBar.navBarScale );
      }

      this.titleLabel.setScaleMagnitude( this.navBarScale );
      this.titleLabel.centerY = this.navBarHeight / 2;
      this.titleLabel.left = 10;

      //Lay out the components from left to right
      if ( this.screens.length !== 1 ) {

        //put the center right in the middle
        this.buttonHBox.centerX = this.navBarWidth / 2;
        this.buttonHBox.top = 2;

        navigationBar.homeIcon.setScaleMagnitude( this.navBarScale );
        navigationBar.homeIcon.top = 2;
        navigationBar.homeIcon.left = navigationBar.buttonHBox.right + 15;
      }
      this.phetButton.setScaleMagnitude( this.navBarScale );
      this.phetButton.right = this.navBarWidth - PhetButton.HORIZONTAL_INSET;
      this.phetButton.bottom = this.navBarHeight - PhetButton.VERTICAL_INSET;
    },
    layout: function( scale, width, height, windowHeight ) {
      this.navBarScale = scale;
      this.navBarWidth = width;
      this.navBarHeight = height;
      this.relayout();
    }} );
} );<commit_msg>Center the home icon vertically and make it a bit larger than the icons and text, see https://github.com/phetsims/joist/issues/127<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the left and the PhET Logo and options menu at the right.
 * For a multi-screen sim, it shows icons for all of the other screens, with the screen name at the left and the PhET Logo and options menu at the right.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var HomeButton = require( 'JOIST/HomeButton' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var inherit = require( 'PHET_CORE/inherit' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );

  /**
   * Create a nav bar.  Layout assumes all of the screen widths are the same.
   * @param {Sim} sim
   * @param {Array<Screen>} screens
   * @param {PropertySet} model see joist.Sim
   * @param {Boolean} whiteColorScheme true if the color scheme should be white, false if it should be black
   * @constructor
   */
  function NavigationBar( sim, screens, model, whiteColorScheme ) {

    var thisNode = this;
    this.screens = screens;

    this.navBarHeight = 40;
    this.navBarScale = 1;
    this.navBarWidth = 768;

    //Renderer must be specified here because the node is added directly to the scene (instead of to some other node that already has svg renderer
    Node.call( this, {renderer: 'svg'} );
    this.background = new Rectangle( 0, 0, 0, 0, {fill: whiteColorScheme ? 'white' : 'black', pickable: false} );
    this.addChild( this.background );

    this.phetButton = new PhetButton( sim, whiteColorScheme, false );
    this.addChild( this.phetButton );

    this.titleLabel = new Text( sim.name, {font: new PhetFont( 18 ), fill: whiteColorScheme ? 'black' : 'white', pickable: false} );
    this.addChild( this.titleLabel );

    if ( screens.length > 1 ) {

      //Create buttons once so we can get their dimensions
      var buttons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton( sim, screen, thisNode.navBarHeight, whiteColorScheme, 0 );
      } );
      var maxWidth = Math.max( 50, _.max( buttons, function( button ) {return button.width;} ).width );

      //Create buttons again with equivalent sizes
      buttons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton( sim, screen, thisNode.navBarHeight, whiteColorScheme, maxWidth );
      } );

      this.buttonHBox = new HBox( {children: buttons, spacing: 4} );
      this.addChild( this.buttonHBox );

      //add the home icon
      this.homeIcon = new HomeButton( whiteColorScheme ? '#222' : 'white', whiteColorScheme ? '#444' : 'gray', whiteColorScheme );
      this.homeIcon.addListener( function() {model.showHomeScreen = true;} );
      this.homeIcon.addPeer( '<input type="button" aria-label="Home Screen">', {click: function() {model.showHomeScreen = true;}, tabIndex: 100} );
      this.addChild( this.homeIcon );
    }
  }

  return inherit( Node, NavigationBar, {
    relayout: function() {
      var navigationBar = this;
      navigationBar.background.rectHeight = this.navBarHeight;
      navigationBar.background.rectWidth = this.navBarWidth;

      if ( this.buttonHBox ) {
        this.buttonHBox.setScaleMagnitude( navigationBar.navBarScale );
      }

      this.titleLabel.setScaleMagnitude( this.navBarScale );
      this.titleLabel.centerY = this.navBarHeight / 2;
      this.titleLabel.left = 10;

      //Lay out the components from left to right
      if ( this.screens.length !== 1 ) {

        //put the center right in the middle
        this.buttonHBox.centerX = this.navBarWidth / 2;
        this.buttonHBox.top = 2;

        //Center the home icon vertically and make it a bit larger than the icons and text, see https://github.com/phetsims/joist/issues/127
        navigationBar.homeIcon.setScaleMagnitude( this.navBarScale * 1.1 );
        navigationBar.homeIcon.centerY = navigationBar.background.rectHeight / 2;
        navigationBar.homeIcon.left = navigationBar.buttonHBox.right + 15;
      }
      this.phetButton.setScaleMagnitude( this.navBarScale );
      this.phetButton.right = this.navBarWidth - PhetButton.HORIZONTAL_INSET;
      this.phetButton.bottom = this.navBarHeight - PhetButton.VERTICAL_INSET;
    },
    layout: function( scale, width, height, windowHeight ) {
      this.navBarScale = scale;
      this.navBarWidth = width;
      this.navBarHeight = height;
      this.relayout();
    }} );
} );<|endoftext|>


<commit_before>/*

Correctly handle PNG transparency in Win IE 5.5 & 6.
http://homepage.ntlworld.com/bobosola. Updated 18-Jan-2006.

Use in <HEAD> with DEFER keyword wrapped in conditional comments:
<!--[if lt IE 7]>
<script defer type="text/javascript" src="pngfix.js"></script>
<![endif]-->

*/

var arVersion = navigator.appVersion.split("MSIE")
var version = parseFloat(arVersion[1])

if ((version >= 5.5) && (document.body.filters))
{
   for(var i=0; i<document.images.length; i++)
   {
      var img = document.images[i]
      var imgName = img.src.toUpperCase()
      if (imgName.substring(imgName.length-3, imgName.length) == "PNG")
      {
         var imgID = (img.id) ? "id='" + img.id + "' " : ""
         var imgClass = (img.className) ? "class='" + img.className + "' " : ""
         var imgTitle = (img.title) ? "title='" + img.title + "' " : "title='" + img.alt + "' "
         var imgStyle = "display:inline-block;" + img.style.cssText
         if (img.align == "left") imgStyle = "float:left;" + imgStyle
         if (img.align == "right") imgStyle = "float:right;" + imgStyle
         if (img.parentElement.href) imgStyle = "cursor:hand;" + imgStyle
         var strNewHTML = "<span " + imgID + imgClass + imgTitle
         + " style=\"" + "width:" + img.width + "px; height:" + img.height + "px;" + imgStyle + ";"
         + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader"
         + "(src=\'" + img.src + "\', sizingMethod='scale');\"></span>"
         img.outerHTML = strNewHTML
         i = i-1
      }
   }
}
<commit_msg>TRASH: IE pngfix.js<commit_after><|endoftext|>


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

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the far left and the PhET button at the far right.
 * For a multi-screen sim, it additionally shows buttons for each screen, and a home button.
 *
 * Layout of NavigationBar adapts to different text widths, icon widths, and numbers of screens, and attempts to
 * perform an "optimal" layout. The sim title is initially constrained to a max percentage of the bar width,
 * and that's used to compute how much space is available for screen buttons.  After creation and layout of the
 * screen buttons, we then compute how much space is actually available for the sim title, and use that to
 * constrain the title's width.
 *
 * The bar is composed of a background (always pixel-perfect), and expandable content (that gets scaled as one part).
 * If we are width-constrained, the navigation bar is in a 'compact' state where the children of the content (e.g.
 * home button, screen buttons, phet menu, title) do not change positions. If we are height-constrained, the amount
 * available to the bar expands, so we lay out the children to fit. See https://github.com/phetsims/joist/issues/283
 * for more details on how this is done.
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  // modules
  var Dimension2 = require( 'DOT/Dimension2' );
  var HomeButton = require( 'JOIST/HomeButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );
  var HomeScreenView = require( 'JOIST/HomeScreenView' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Screen = require( 'JOIST/Screen' );
  var Text = require( 'SCENERY/nodes/Text' );

  // constants
  var NAVIGATION_BAR_SIZE = new Dimension2( HomeScreenView.LAYOUT_BOUNDS.width, 40 );
  var TITLE_LEFT_MARGIN = 10;
  var TITLE_RIGHT_MARGIN = 25;
  var PHET_BUTTON_LEFT_MARGIN = TITLE_RIGHT_MARGIN;
  var PHET_BUTTON_RIGHT_MARGIN = PhetButton.HORIZONTAL_INSET; // same position as PhetButton on home screen
  var PHET_BUTTON_BOTTOM_MARGIN = PhetButton.VERTICAL_INSET; // same position as PhetButton on home screen
  var HOME_BUTTON_LEFT_MARGIN = 8;
  var SCREEN_BUTTON_SPACING = 0;
  var MINIMUM_SCREEN_BUTTON_WIDTH = 60; // Make sure each button is at least a minimum width so they don't get too close
                                        // together, see #279

  /**
   * Creates a nav bar.
   * @param {Sim} sim
   * @param {Screen[]} screens
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBar( sim, screens, options ) {

    options = _.extend( {
      tandem: null
    }, options );

    // all icons must have a valid aspect ratio, which matches either navbar or homescreen icon dimensions, see joist#263
    if ( screens.length > 1 ) {
      var navbarIconAspectRatio = Screen.NAVBAR_ICON_SIZE.width / Screen.NAVBAR_ICON_SIZE.height;
      var homeScreenIconAspectRatio = Screen.HOME_SCREEN_ICON_SIZE.width / Screen.HOME_SCREEN_ICON_SIZE.height;
      for ( var screenIndex = 0; screenIndex < screens.length; screenIndex++ ) {
        var screen = screens[ screenIndex ];
        var iconAspectRatio = screen.navigationBarIcon.width / screen.navigationBarIcon.height;
        var validAspectRatio = iconAspectRatio === navbarIconAspectRatio || iconAspectRatio === homeScreenIconAspectRatio;
        assert( validAspectRatio, 'NavigationBar icons for screen ' + screenIndex + ' did not have a valid aspect ratio.' );
      }
    }

    this.screens = screens;

    Node.call( this );

    // The bar's background (resized in layout)
    this.background = new Rectangle( 0, 0, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height );
    sim.lookAndFeel.navigationBarFillProperty.linkAttribute( this.background, 'fill' );
    this.addChild( this.background );

    // Everything else besides the background in the navigation bar (used for scaling)
    this.barContents = new Node();
    this.addChild( this.barContents );

    // Sim title
    var title = new Text( sim.name, {
      font: new PhetFont( 18 )
    } );
    sim.lookAndFeel.navigationBarTextFillProperty.linkAttribute( title, 'fill' );
    this.barContents.addChild( title );

    // PhET button
    this.phetButton = new PhetButton( sim, sim.lookAndFeel.navigationBarFillProperty, sim.lookAndFeel.navigationBarTextFillProperty, {
      tandem: options.tandem ? options.tandem.createTandem( 'phetButton' ) : null
    } );
    this.barContents.addChild( this.phetButton );

    if ( screens.length === 1 ) {
      /* single-screen sim */

      // title can occupy all space to the left of the PhET button
      title.maxWidth = HomeScreenView.LAYOUT_BOUNDS.width - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN - this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN;
    }
    else {
      /* multi-screen sim */

      // Start with the assumption that the title can occupy (at most) this percentage of the bar.
      var maxTitleWidth = Math.min( title.width, 0.25 * HomeScreenView.LAYOUT_BOUNDS.width );

      // Create the home button
      this.homeButton = new HomeButton( NAVIGATION_BAR_SIZE.height, sim.lookAndFeel.navigationBarFillProperty, {
        listener: function() {
          sim.showHomeScreen = true;
        },
        tandem: options.tandem && options.tandem.createTandem( 'homeButton' )
      } );
      this.barContents.addChild( this.homeButton );

      /*
       * Allocate remaining horizontal space equally for screen buttons, assuming they will be centered in the navbar.
       * Computations here reflect the left-to-right layout of the navbar.
       */
      // available width left of center
      var availableLeft = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - TITLE_LEFT_MARGIN - maxTitleWidth - TITLE_RIGHT_MARGIN;

      // available width right of center
      var availableRight = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - HOME_BUTTON_LEFT_MARGIN - this.homeButton.width -
                           PHET_BUTTON_LEFT_MARGIN - this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN;

      // total available width for the screen buttons when they are centered
      var availableTotal = 2 * Math.min( availableLeft, availableRight );

      // width per screen button
      var screenButtonWidth = ( availableTotal - ( screens.length - 1 ) * SCREEN_BUTTON_SPACING ) / screens.length;

      // Create the screen buttons
      var screenButtons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton(
          sim.lookAndFeel.navigationBarFillProperty,
          sim.screenIndexProperty,
          sim.screens,
          screen,
          NAVIGATION_BAR_SIZE.height, {
            maxButtonWidth: screenButtonWidth,
            tandem: options.tandem && options.tandem.createTandem( screen.tandemScreenName + 'Button' )
          } );
      } );

      // Layout out screen buttons horizontally, with equal distance between their centers
      // Make sure each button is at least a minimum size, so they don't get too close together, see #279
      var maxScreenButtonWidth = Math.max( MINIMUM_SCREEN_BUTTON_WIDTH, _.max( screenButtons, function( button ) {
        return button.width;
      } ).width );

      // Compute the distance between *centers* of each button
      var spaceBetweenButtons = maxScreenButtonWidth + SCREEN_BUTTON_SPACING;
      for ( var i = 0; i < screenButtons.length; i++ ) {

        // Equally space the centers of the buttons around the origin of their parent (screenButtonsContainer)
        screenButtons[ i ].centerX = spaceBetweenButtons * ( i - ( screenButtons.length - 1 ) / 2 );
      }

      // Put all screen buttons under a parent, to simplify layout
      this.screenButtonsContainer = new Node( {
        children: screenButtons,
        // NOTE: these layout settings are duplicated in layout(), but are necessary due to title's maxWidth requiring layout
        x: this.background.centerX, // since we have buttons centered around our origin, this centers the buttons
        centerY: this.background.centerY,
        maxWidth: availableTotal // in case we have so many screens that the screen buttons need to be scaled down
      } );
      this.barContents.addChild( this.screenButtonsContainer );

      // Now determine the actual width constraint for the sim title.
      title.maxWidth = this.screenButtonsContainer.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN;
    }

    // initial layout (that doesn't need to change when we are re-layed out)
    title.left = TITLE_LEFT_MARGIN;
    title.centerY = NAVIGATION_BAR_SIZE.height / 2;
    this.phetButton.bottom = NAVIGATION_BAR_SIZE.height - PHET_BUTTON_BOTTOM_MARGIN;
    if ( this.screens.length !== 1 ) {
      this.screenButtonsContainer.centerY = NAVIGATION_BAR_SIZE.height / 2;
      this.homeButton.centerY = NAVIGATION_BAR_SIZE.height / 2;
    }

    this.layout( 1, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height );
  }

  return inherit( Node, NavigationBar, {

    /**
     * Called when the navigation bar layout needs to be updated, typically when the browser window is resized.
     * @param {number} scale
     * @param {number} width
     * @param {number} height
     * @public
     */
    layout: function( scale, width, height ) {

      // resize the background
      this.background.rectWidth = width;
      this.background.rectHeight = height;

      // scale the entire bar contents
      this.barContents.setScaleMagnitude( scale );

      // determine our local-coordinate 'right' side of the screen, so we can expand if necessary
      var right;
      if ( NAVIGATION_BAR_SIZE.width * scale < width ) {
        // expanded
        right = width / scale;
      }
      else {
        // compact
        right = NAVIGATION_BAR_SIZE.width;
      }

      // horizontal positioning
      this.phetButton.right = right - PHET_BUTTON_RIGHT_MARGIN;

      // For multi-screen sims ...
      if ( this.screens.length !== 1 ) {
        // screen buttons centered.  The screen buttons are centered around the origin in the screenButtonsContainer, so
        // the screenButtonsContainer can be put at x=center of the navbar
        this.screenButtonsContainer.x = right / 2;

        // home button to the right of screen buttons
        this.homeButton.left = this.screenButtonsContainer.right + HOME_BUTTON_LEFT_MARGIN;
      }
    }
  }, {
    NAVIGATION_BAR_SIZE: NAVIGATION_BAR_SIZE
  } );
} );<commit_msg>fix assertion error introduced by @samreid in previous commit<commit_after>// Copyright 2002-2015, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the far left and the PhET button at the far right.
 * For a multi-screen sim, it additionally shows buttons for each screen, and a home button.
 *
 * Layout of NavigationBar adapts to different text widths, icon widths, and numbers of screens, and attempts to
 * perform an "optimal" layout. The sim title is initially constrained to a max percentage of the bar width,
 * and that's used to compute how much space is available for screen buttons.  After creation and layout of the
 * screen buttons, we then compute how much space is actually available for the sim title, and use that to
 * constrain the title's width.
 *
 * The bar is composed of a background (always pixel-perfect), and expandable content (that gets scaled as one part).
 * If we are width-constrained, the navigation bar is in a 'compact' state where the children of the content (e.g.
 * home button, screen buttons, phet menu, title) do not change positions. If we are height-constrained, the amount
 * available to the bar expands, so we lay out the children to fit. See https://github.com/phetsims/joist/issues/283
 * for more details on how this is done.
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  // modules
  var Dimension2 = require( 'DOT/Dimension2' );
  var HomeButton = require( 'JOIST/HomeButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );
  var HomeScreenView = require( 'JOIST/HomeScreenView' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Screen = require( 'JOIST/Screen' );
  var Text = require( 'SCENERY/nodes/Text' );

  // constants
  var NAVIGATION_BAR_SIZE = new Dimension2( HomeScreenView.LAYOUT_BOUNDS.width, 40 );
  var TITLE_LEFT_MARGIN = 10;
  var TITLE_RIGHT_MARGIN = 25;
  var PHET_BUTTON_LEFT_MARGIN = TITLE_RIGHT_MARGIN;
  var PHET_BUTTON_RIGHT_MARGIN = PhetButton.HORIZONTAL_INSET; // same position as PhetButton on home screen
  var PHET_BUTTON_BOTTOM_MARGIN = PhetButton.VERTICAL_INSET; // same position as PhetButton on home screen
  var HOME_BUTTON_LEFT_MARGIN = 8;
  var SCREEN_BUTTON_SPACING = 0;
  var MINIMUM_SCREEN_BUTTON_WIDTH = 60; // Make sure each button is at least a minimum width so they don't get too close
                                        // together, see #279

  /**
   * Creates a nav bar.
   * @param {Sim} sim
   * @param {Screen[]} screens
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBar( sim, screens, options ) {

    options = _.extend( {
      tandem: null
    }, options );

    // all icons must have a valid aspect ratio, which matches either navbar or homescreen icon dimensions, see joist#263
    if ( screens.length > 1 ) {
      var navbarIconAspectRatio = Screen.NAVBAR_ICON_SIZE.width / Screen.NAVBAR_ICON_SIZE.height;
      var homeScreenIconAspectRatio = Screen.HOME_SCREEN_ICON_SIZE.width / Screen.HOME_SCREEN_ICON_SIZE.height;
      for ( var screenIndex = 0; screenIndex < screens.length; screenIndex++ ) {
        var screen = screens[ screenIndex ];
        var iconAspectRatio = screen.navigationBarIcon.width / screen.navigationBarIcon.height;
        var validAspectRatio = iconAspectRatio === navbarIconAspectRatio || iconAspectRatio === homeScreenIconAspectRatio;
        assert && assert( validAspectRatio, 'NavigationBar icons for screen ' + screenIndex + ' did not have a valid aspect ratio.' );
      }
    }

    this.screens = screens;

    Node.call( this );

    // The bar's background (resized in layout)
    this.background = new Rectangle( 0, 0, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height );
    sim.lookAndFeel.navigationBarFillProperty.linkAttribute( this.background, 'fill' );
    this.addChild( this.background );

    // Everything else besides the background in the navigation bar (used for scaling)
    this.barContents = new Node();
    this.addChild( this.barContents );

    // Sim title
    var title = new Text( sim.name, {
      font: new PhetFont( 18 )
    } );
    sim.lookAndFeel.navigationBarTextFillProperty.linkAttribute( title, 'fill' );
    this.barContents.addChild( title );

    // PhET button
    this.phetButton = new PhetButton( sim, sim.lookAndFeel.navigationBarFillProperty, sim.lookAndFeel.navigationBarTextFillProperty, {
      tandem: options.tandem ? options.tandem.createTandem( 'phetButton' ) : null
    } );
    this.barContents.addChild( this.phetButton );

    if ( screens.length === 1 ) {
      /* single-screen sim */

      // title can occupy all space to the left of the PhET button
      title.maxWidth = HomeScreenView.LAYOUT_BOUNDS.width - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN - this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN;
    }
    else {
      /* multi-screen sim */

      // Start with the assumption that the title can occupy (at most) this percentage of the bar.
      var maxTitleWidth = Math.min( title.width, 0.25 * HomeScreenView.LAYOUT_BOUNDS.width );

      // Create the home button
      this.homeButton = new HomeButton( NAVIGATION_BAR_SIZE.height, sim.lookAndFeel.navigationBarFillProperty, {
        listener: function() {
          sim.showHomeScreen = true;
        },
        tandem: options.tandem && options.tandem.createTandem( 'homeButton' )
      } );
      this.barContents.addChild( this.homeButton );

      /*
       * Allocate remaining horizontal space equally for screen buttons, assuming they will be centered in the navbar.
       * Computations here reflect the left-to-right layout of the navbar.
       */
      // available width left of center
      var availableLeft = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - TITLE_LEFT_MARGIN - maxTitleWidth - TITLE_RIGHT_MARGIN;

      // available width right of center
      var availableRight = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - HOME_BUTTON_LEFT_MARGIN - this.homeButton.width -
                           PHET_BUTTON_LEFT_MARGIN - this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN;

      // total available width for the screen buttons when they are centered
      var availableTotal = 2 * Math.min( availableLeft, availableRight );

      // width per screen button
      var screenButtonWidth = ( availableTotal - ( screens.length - 1 ) * SCREEN_BUTTON_SPACING ) / screens.length;

      // Create the screen buttons
      var screenButtons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton(
          sim.lookAndFeel.navigationBarFillProperty,
          sim.screenIndexProperty,
          sim.screens,
          screen,
          NAVIGATION_BAR_SIZE.height, {
            maxButtonWidth: screenButtonWidth,
            tandem: options.tandem && options.tandem.createTandem( screen.tandemScreenName + 'Button' )
          } );
      } );

      // Layout out screen buttons horizontally, with equal distance between their centers
      // Make sure each button is at least a minimum size, so they don't get too close together, see #279
      var maxScreenButtonWidth = Math.max( MINIMUM_SCREEN_BUTTON_WIDTH, _.max( screenButtons, function( button ) {
        return button.width;
      } ).width );

      // Compute the distance between *centers* of each button
      var spaceBetweenButtons = maxScreenButtonWidth + SCREEN_BUTTON_SPACING;
      for ( var i = 0; i < screenButtons.length; i++ ) {

        // Equally space the centers of the buttons around the origin of their parent (screenButtonsContainer)
        screenButtons[ i ].centerX = spaceBetweenButtons * ( i - ( screenButtons.length - 1 ) / 2 );
      }

      // Put all screen buttons under a parent, to simplify layout
      this.screenButtonsContainer = new Node( {
        children: screenButtons,
        // NOTE: these layout settings are duplicated in layout(), but are necessary due to title's maxWidth requiring layout
        x: this.background.centerX, // since we have buttons centered around our origin, this centers the buttons
        centerY: this.background.centerY,
        maxWidth: availableTotal // in case we have so many screens that the screen buttons need to be scaled down
      } );
      this.barContents.addChild( this.screenButtonsContainer );

      // Now determine the actual width constraint for the sim title.
      title.maxWidth = this.screenButtonsContainer.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN;
    }

    // initial layout (that doesn't need to change when we are re-layed out)
    title.left = TITLE_LEFT_MARGIN;
    title.centerY = NAVIGATION_BAR_SIZE.height / 2;
    this.phetButton.bottom = NAVIGATION_BAR_SIZE.height - PHET_BUTTON_BOTTOM_MARGIN;
    if ( this.screens.length !== 1 ) {
      this.screenButtonsContainer.centerY = NAVIGATION_BAR_SIZE.height / 2;
      this.homeButton.centerY = NAVIGATION_BAR_SIZE.height / 2;
    }

    this.layout( 1, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height );
  }

  return inherit( Node, NavigationBar, {

    /**
     * Called when the navigation bar layout needs to be updated, typically when the browser window is resized.
     * @param {number} scale
     * @param {number} width
     * @param {number} height
     * @public
     */
    layout: function( scale, width, height ) {

      // resize the background
      this.background.rectWidth = width;
      this.background.rectHeight = height;

      // scale the entire bar contents
      this.barContents.setScaleMagnitude( scale );

      // determine our local-coordinate 'right' side of the screen, so we can expand if necessary
      var right;
      if ( NAVIGATION_BAR_SIZE.width * scale < width ) {
        // expanded
        right = width / scale;
      }
      else {
        // compact
        right = NAVIGATION_BAR_SIZE.width;
      }

      // horizontal positioning
      this.phetButton.right = right - PHET_BUTTON_RIGHT_MARGIN;

      // For multi-screen sims ...
      if ( this.screens.length !== 1 ) {
        // screen buttons centered.  The screen buttons are centered around the origin in the screenButtonsContainer, so
        // the screenButtonsContainer can be put at x=center of the navbar
        this.screenButtonsContainer.x = right / 2;

        // home button to the right of screen buttons
        this.homeButton.left = this.screenButtonsContainer.right + HOME_BUTTON_LEFT_MARGIN;
      }
    }
  }, {
    NAVIGATION_BAR_SIZE: NAVIGATION_BAR_SIZE
  } );
} );<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');
var fsExtra = require('fs-extra');
var utils = require('./utils');
var filemeta = require('./filemeta');
var filetype = require('../content/src/filetype');

exports.handleSave = function(req, res, app) {
  var THUMB_DIR = '.thumbs/';

  var data = utils.param(req, 'data');
  var meta = utils.param(req, 'meta');
  var thumbnail = utils.param(req, 'thumbnail');
  var sourcefile = utils.param(req, 'source');
  var mode = utils.param(req, 'mode');
  var conditional = utils.param(req, 'conditional');
  var key = utils.param(req, 'key');
  var sourcekey = utils.param(req, 'sourcekey', key);

  try {
    var user = res.locals.owner;
    var filename = utils.param(req, "file", utils.filenameFromUri(req));
    var thumbname = path.join(path.dirname(filename), THUMB_DIR,
                              path.basename(filename) + '.png');

    /*
    console.log({
      user: user,
      key: key,
      filename: filename,
      mode: mode,
      data: data,
      sourcefile: sourcefile,
      conditional: conditional});
    */

    try {
      fsExtra.removeSync(utils.getRootCacheName(app));
    }
    catch (e) { }

    //
    // Validate parameters
    //

    if (data && sourcefile) {
      utils.errorExit('Cannot supply both data and source');
    }

    if (mode) {
      switch (mode) {
        case 'mv':
          if (!sourcefile) {
            utils.errorExit('No source specified for mv');
          }
          break;
        case 'setkey':
          if (!data && sourcefile) {
            utils.errorExit('Invalid parameters specified for setkey');
          }
          break;
        case 'rmtree':
          if (data || sourcefile) {
            utils.errorExit('Either data or source specified for rmtree');
          }
          break;
        default:
          utils.errorExit('Unknown mode type');
      }
    }

    if (conditional) {
      conditional = new Date(conditional);
    }

    // validate username
    var userdir = null;
    if (user) {
      utils.validateUserName(user);
      filename = path.join(user, filename);
      thumbname = path.join(user, thumbname);
      userdir = utils.getUserHomeDir(user, app);
    }

    var topdir = false;
    if (!utils.isFileNameValid(filename, true)) {
      if (mode == 'setkey' ||
          (!data && (mode == 'rmtree' || mode == 'mv' || !sourcefile)) &&
          /^[\w][\w\\-]*\/?$/.test(filename)) {
        topdir = true;
      }
      else {
        utils.errorExit('Bad filename ' + filename);
      }
    }

    // Parse and validate meta.
    if (meta != null) {
      try {
        meta = JSON.parse(meta);
      } catch (e) {
        utils.errorExit('Malformed json meta: ' + meta);
      }
    }

    if (filetype.isDefaultMeta(meta)) {
      meta = null;
    }

    var absfile = utils.makeAbsolute(filename, app);
    var absthumb = utils.makeAbsolute(thumbname, app);

    //
    // Validate that users key matches the supplied key
    //

    if (!isValidKey(user, key, app)) {
      var msg = (key) ? 'Incorrect password.' : 'Password protected.';
      res.json({error: msg, 'needauth': 'key'});
      return;
    }

    //
    // Handle setkey
    //

    if (mode == 'setkey') {
      if (!topdir) {
        utils.errorExit('Can only set key on a top-level user directory.');
      }

      doSetKey(user, key, data, res, app);
      res.json((data) ? {keyset: user} : {keycleared: user});
      return;
    }

    //
    // Handle the copy/move case
    //

    if (sourcefile) {
      if (!/^(?:[\w][\w\.\-]*)(?:\/[\w][\w\.\-]*)*\/?$/.test(sourcefile)) {
        utils.errorExit('Bad source filename: ' + sourcefile);
      }

      sourceuser = filenameuser(sourcefile);

      var absSourceFile = utils.makeAbsolute(sourcefile, app);
      if (!fs.existsSync(absSourceFile)) {
        utils.errorExit('Source file does not exist. ' + sourcefile);
      }

      // Only directories can be copied or moved to the top
      if (topdir && !fs.statSync(absSourceFile).isDirectory()) {
        utils.errorExit('Bad filename. ' + filename);
      }

      // mv requires authz on the source dir
      if (mode == 'mv') {
        if (!isValidKey(sourceuser, sourcekey, app)) {
          var msg = (!key) ?
              'Source password protected.' : 'Incorrect source password.';
          res.json({error: msg, 'auth': 'key'});
          return;
        }
      }

      // Create target parent directory if needed
      if (!fs.existsSync(path.dirname(absfile)) ||
          !fs.statSync(path.dirname(absfile)).isDirectory()) {
        checkReservedUser(user, app);
        tryToMkdirsSync(absfile);
      }

      // move case
      if (mode == 'mv') {
        if (fs.existsSync(absfile)) {
          utils.errorExit('Cannot replace existing file: ' + filename);
        }

        try {
          fs.renameSync(absSourceFile, absfile);

          // Cleanup directories if necessary
          removeDirsSync(path.dirname(absSourceFile));

          // Remove .key if present, because we don't want to
          // propagate password data
          if (utils.isPresent(path.join(absfile, '.key'))) {
            fsExtra.removeSync(path.join(absfile, '.key'));
          }
        }
        catch (e) {
          utils.errorExit('Could not move ' + sourcefile + ' to ' + filename);
        }
      }
      else {
        // Copy case
        try {
          // Are we copying a directory?
          if (utils.isPresent(absSourceFile, 'dir')) {
            if (fs.existsSync(absfile)) {
              utils.errorExit(
                  'Cannot overwrite existing directory ' + filename);
            }

            fsExtra.copySync(absSourceFile, absfile);

            // Remove .key if present, because we don't want to
            // propagate password data.
            if (utils.isPresent(path.join(absfile, '.key'))) {
              fsExtra.removeSync(path.join(absfile, '.key'));
            }
          }
          else {
            fsExtra.copySync(absSourceFile, absfile);
          }
        }
        catch (e) {
          utils.errorExit('Could not copy ' + sourcefile + ' to ' + filename);
        }
      }

      touchUserDir(userdir);
      res.json({saved: '/' + filename});
      return;
    }

    //
    // Enforce the conditional request if present
    //

    if (conditional) {
      if (fs.existsSync(absfile)) {
        mtime = fs.statSync(absfile).mtime.getTime();
        if (mtime > conditional) {
            res.json({error: 'Did not overwrite newer file.',
                      newer: mtime});
            return;
        }
      }
    }

    //
    // Handle the delete case
    //

    if (!data && !(meta && (meta.html || meta.css))) {
        //if (!req.body.hasOwnProperty('data')) {
        //utils.errorExit('Missing data= form field argument.');
        //}

      if (fs.existsSync(absthumb)) {
        tryToRemoveSync(absthumb);
      }

      if (fs.existsSync(absfile)) {
        tryToRemoveSync(absfile);
      }

      if (userdir != absfile) {
        touchUserDir(userdir);
      }

      res.json({'deleted' : filename});
      return;
    }

    // Validate data
    if (data.length > 1024 * 1024) {
      utils.errorExit('Data too large.');
    }

    //
    // Finally handle the create/replace case
    //

    if (!fs.existsSync(path.dirname(absfile)) ||
        !fs.statSync(path.dirname(absfile)).isDirectory()) {
      checkReservedUser(user, app);
      tryToMkdirsSync(absfile);
    }

    var content = filemeta.printMetaString(data, meta);
    fd = tryToWriteFileSync(absfile, content);

    // If thumbnail exists and it is valid, remove the data url header.
    // and then save as png.
    if (thumbnail && /^data:image\/png;base64,/.test(thumbnail)) {
      var base64data = thumbnail.replace(/^data:image\/png;base64,/, '');
      tryToMkdirsSync(absthumb);
      tryToWriteFileSync(absthumb, base64data, { encoding: 'base64' });
    }

    var statObj = fs.statSync(absfile);
    touchUserDir(userdir);
    res.json({
      saved: '/' + filename,
      mtime: statObj.mtime.getTime(),
      size: statObj.size
    });
  }
  catch (e) {
    if (e instanceof utils.ImmediateReturnError) {
      res.json(e.jsonObj);
    }
    else {
      throw e;
    }
  }
}

function tryToWriteFileSync(absfilename, data, options) {
  try {
    return fs.writeFileSync(absfilename, data, options);
  } catch (e) {
    utils.errorExit('Error writing file: '  absfilename);
  }
}

function tryToMkdirsSync(absfilename) {
  try {
    fsExtra.mkdirsSync(path.dirname(absfilename));
  } catch (e) {
    utils.errorExit('Could not create dir: '  path.dirname(absfilename));
  }
}

function tryToRemoveSync(absfilename) {
  try {
    fsExtra.removeSync(absfilename);
  } catch (e) {
    utils.errorExit('Could not remove: '  absfilename);
  }

  try {
    removeDirsSync(path.dirname(absfilename));
  } catch (e) { }
}

function touchUserDir(userdir) {
  try {
    var now = new Date;
    fs.utimesSync(userdir, now, now);
  }
  catch (e) { }
}

function filenameuser(filename) {
  var m = filename.match(/^([\w][\w\.\-]*)(?:\/.*)?$/);

  return (m) ? m[1] : null;
}

function removeDirsSync(dirStart) {
  for (var dir = dirStart; ; dir = path.dirname(dir)) {
    try {
      fs.rmdirSync(dir);
    } catch (e) {
      // Failed to remove dir, assume not empty.
      break;
    }
  }
}

function isValidKey(user, key, app) {
  //
  // keydir is the directory containing the hashed user password.
  // It's a subdir off the user home directory called '.key'.
  // Contents of this directory are files that are named
  // with the hashed user password
  //

  var keydir = utils.getKeyDir(user, app);
  var statObj = null;

  if (!utils.isPresent(keydir, 'dir')) {
    return true;
  }

  // Now we know its a directory
  var keys = fs.readdirSync(keydir);
  if (!keys || keys.length == 0) {
    // No key files, must mean no password for user.
    // So assume that this is valid.
    return true;
  }

  if (key) {
    for (var i = 0; i < keys.length; i++) {
      //
      // Password files are named with 'k' + the hashed password.
      // See doSetKey() for implementation.  So check the substring
      // offset with 1 to ignore the starting 'k'
      //
      if (key.indexOf(keys[i].substring(1)) == 0) {
        return true;
      }
    }
  }
}

//
// Create a file with the hashed user password in the
// user key dir.  This is called when the user sets or changes
// their password.
//
function doSetKey(user, oldkey, newkey, res, app) {
  if (oldkey == newkey) {
    return;
  }

  var keydir = utils.getKeyDir(user, app);

  try {
    // Create directory if not present
    if (!fs.existsSync(keydir)) {
      checkReservedUser(user, app);
      fsExtra.mkdirsSync(keydir);
    }

    if (oldkey) {
      //
      // Delete old password file if present
      //

      var keys = fs.readdirSync(keydir);

      for (var i = 0; i < keys.length; i++) {
        if (oldkey.indexOf(keys[i].substring(1)) == 0) {
          fs.unlink(path.join(keydir, keys[i]));
        }
      }
    }

    if (newkey) {
      // Now create new password file
      keyfile = path.join(keydir, 'k' + newkey);
      fs.closeSync(fs.openSync(keyfile, 'w'));
    }
  }
  catch (e) {
    if (e instanceof utils.ImmediateReturnError) {
      throw e;
    }
    utils.errorExit('Could not set password.');
  }
}

function letterComplexity(s) {
  var maxcount = 0, uniqcount = 0, dupcount = 0, last = null, count = {}, j, c;
  for (j = 0; j < s.length; ++j) {
    c = s.charAt(j);
    if (!(c in count)) {
      uniqcount += 1;
      count[c] = 0;
    }
    count[c] += 1;
    maxcount = Math.max(count[c], maxcount);
    if (c == last) { dupcount += 1; }
    last = c;
  }
  return uniqcount && (uniqcount / (maxcount + dupcount));
}

function checkReservedUser(user, app) {
  var datadirAbs = path.resolve(app.locals.config.dirs.datadir);

  if (fs.existsSync(path.join(datadirAbs, user))) {
    return;
  }

  if (user != user.toLowerCase()) {
    utils.errorExit('Username should be lowercase.');
  }

  var normalized = user.toLowerCase();
  if (fs.existsSync(path.join(datadirAbs, normalized))) {
    utils.errorExit('Username is reserved.');
  }

  // Prohibit low-complexity usernames (like "aaaaaa")
  if (letterComplexity(normalized) <= 1) {
    utils.errorExit('Username is reserved.');
  }

  // Also check possible variations of badwords
  var normalizedi = translate(normalized, '013456789', 'oieasbtbg');
  if (normalized != normalizedi &&
      fs.existsSync(path.join(datadirAbs, normalizedi))) {
    utils.errorExit('Username is reserved.');
  }

  var normalizedl = translate(normalized, '013456789', 'oleasbtbg');
  if (normalizedl != normalized &&
      fs.existsSync(path.join(datadirAbs, normalizedl))) {
    utils.errorExit('Username is reserved.');
  }

  var checkwords = [normalized, normalizedi, normalizedl];
  var badwords =
      fs.readFileSync(path.join(__dirname, 'bad-words.txt'), 'utf8').
          split(/\n/);
  var badsubstrings =
      fs.readFileSync(path.join(__dirname, 'bad-substrings.txt'), 'utf8').
          split(/\n/);

  for (var i = 0; i < checkwords.length; i++) {
    for (var j = 0; j < badwords.length; j++) {
      if (badwords[j].length > 0 && checkwords[i] == badwords[j]) {
        utils.errorExit('Username is reserved.');
      }
    }
    for (var j = 0; j < badsubstrings.length; j++) {
      if (badsubstrings[j].length > 0 &&
          checkwords[i].indexOf(badsubstrings[j]) != -1) {
        utils.errorExit('Username is reserved.');
      }
    }
  }
}

function translate(source, from, to) {
  var fromArr = from.split('');
  var toArr = to.split('');
  var copy = new String(source);

  if (fromArr.length != toArr.length) {
    utils.errorExit('Uh oh, parameters to translate are incorrect.');
  }

  for (var i = 0; i < fromArr.length; i++) {
    var x = copy.indexOf(fromArr[i]);
    if (x != -1) {
      // Match found, so replace it
      copy[x] = toArr[i];
    }
  }
  return copy;
}
<commit_msg>Move/rename/copy the thumb when move/rename/copy the file<commit_after>var path = require('path');
var fs = require('fs');
var fsExtra = require('fs-extra');
var utils = require('./utils');
var filemeta = require('./filemeta');
var filetype = require('../content/src/filetype');

exports.handleSave = function(req, res, app) {
  var THUMB_DIR = '.thumbs/';

  var data = utils.param(req, 'data');
  var meta = utils.param(req, 'meta');
  var thumbnail = utils.param(req, 'thumbnail');
  var sourcefile = utils.param(req, 'source');
  var mode = utils.param(req, 'mode');
  var conditional = utils.param(req, 'conditional');
  var key = utils.param(req, 'key');
  var sourcekey = utils.param(req, 'sourcekey', key);

  try {
    var user = res.locals.owner;
    var filename = utils.param(req, "file", utils.filenameFromUri(req));
    var thumbname = path.join(path.dirname(filename), THUMB_DIR,
                              path.basename(filename) + '.png');

    /*
    console.log({
      user: user,
      key: key,
      filename: filename,
      mode: mode,
      data: data,
      sourcefile: sourcefile,
      conditional: conditional});
    */

    try {
      fsExtra.removeSync(utils.getRootCacheName(app));
    }
    catch (e) { }

    //
    // Validate parameters
    //

    if (data && sourcefile) {
      utils.errorExit('Cannot supply both data and source');
    }

    if (mode) {
      switch (mode) {
        case 'mv':
          if (!sourcefile) {
            utils.errorExit('No source specified for mv');
          }
          break;
        case 'setkey':
          if (!data && sourcefile) {
            utils.errorExit('Invalid parameters specified for setkey');
          }
          break;
        case 'rmtree':
          if (data || sourcefile) {
            utils.errorExit('Either data or source specified for rmtree');
          }
          break;
        default:
          utils.errorExit('Unknown mode type');
      }
    }

    if (conditional) {
      conditional = new Date(conditional);
    }

    // validate username
    var userdir = null;
    if (user) {
      utils.validateUserName(user);
      filename = path.join(user, filename);
      thumbname = path.join(user, thumbname);
      userdir = utils.getUserHomeDir(user, app);
    }

    var topdir = false;
    if (!utils.isFileNameValid(filename, true)) {
      if (mode == 'setkey' ||
          (!data && (mode == 'rmtree' || mode == 'mv' || !sourcefile)) &&
          /^[\w][\w\\-]*\/?$/.test(filename)) {
        topdir = true;
      }
      else {
        utils.errorExit('Bad filename ' + filename);
      }
    }

    // Parse and validate meta.
    if (meta != null) {
      try {
        meta = JSON.parse(meta);
      } catch (e) {
        utils.errorExit('Malformed json meta: ' + meta);
      }
    }

    if (filetype.isDefaultMeta(meta)) {
      meta = null;
    }

    var absfile = utils.makeAbsolute(filename, app);
    var absthumb = utils.makeAbsolute(thumbname, app);

    //
    // Validate that users key matches the supplied key
    //

    if (!isValidKey(user, key, app)) {
      var msg = (key) ? 'Incorrect password.' : 'Password protected.';
      res.json({error: msg, 'needauth': 'key'});
      return;
    }

    //
    // Handle setkey
    //

    if (mode == 'setkey') {
      if (!topdir) {
        utils.errorExit('Can only set key on a top-level user directory.');
      }

      doSetKey(user, key, data, res, app);
      res.json((data) ? {keyset: user} : {keycleared: user});
      return;
    }

    //
    // Handle the copy/move case
    //

    if (sourcefile) {
      if (!/^(?:[\w][\w\.\-]*)(?:\/[\w][\w\.\-]*)*\/?$/.test(sourcefile)) {
        utils.errorExit('Bad source filename: ' + sourcefile);
      }

      sourceuser = filenameuser(sourcefile);

      var absSourceFile = utils.makeAbsolute(sourcefile, app);
      var sourcethumb = path.join(path.dirname(sourcefile), THUMB_DIR,
                                  path.basename(sourcefile) + '.png');
      var absSourceThumb = utils.makeAbsolute(sourcethumb, app);
      var sourceThumbExists = fs.existsSync(path.dirname(absSourceThumb));
      if (!fs.existsSync(absSourceFile)) {
        utils.errorExit('Source file does not exist. ' + sourcefile);
      }

      // Only directories can be copied or moved to the top
      if (topdir && !fs.statSync(absSourceFile).isDirectory()) {
        utils.errorExit('Bad filename. ' + filename);
      }

      // mv requires authz on the source dir
      if (mode == 'mv') {
        if (!isValidKey(sourceuser, sourcekey, app)) {
          var msg = (!key) ?
              'Source password protected.' : 'Incorrect source password.';
          res.json({error: msg, 'auth': 'key'});
          return;
        }
      }

      // Create target parent directory if needed
      if (!fs.existsSync(path.dirname(absfile)) ||
          !fs.statSync(path.dirname(absfile)).isDirectory()) {
        checkReservedUser(user, app);
        tryToMkdirsSync(absfile);
      }

      // If the source file has a thumb, create dir for the destination thumb.
      if (sourceThumbExists) {
        tryToMkdirsSync(absthumb);
      }

      // move case
      if (mode == 'mv') {
        if (fs.existsSync(absfile)) {
          utils.errorExit('Cannot replace existing file: ' + filename);
        }

        try {
          fs.renameSync(absSourceFile, absfile);

          // Move the thumb along the way if it exists.
          if (sourceThumbExists) {
            fs.renameSync(absSourceThumb, absthumb);
            removeDirsSync(path.dirname(absSourceThumb));
          }

          // Cleanup directories if necessary
          removeDirsSync(path.dirname(absSourceFile));

          // Remove .key if present, because we don't want to
          // propagate password data
          if (utils.isPresent(path.join(absfile, '.key'))) {
            fsExtra.removeSync(path.join(absfile, '.key'));
          }
        }
        catch (e) {
          utils.errorExit('Could not move ' + sourcefile + ' to ' + filename);
        }
      }
      else {
        // Copy case
        try {
          // Are we copying a directory?
          if (utils.isPresent(absSourceFile, 'dir')) {
            if (fs.existsSync(absfile)) {
              utils.errorExit(
                  'Cannot overwrite existing directory ' + filename);
            }

            fsExtra.copySync(absSourceFile, absfile);

            // Remove .key if present, because we don't want to
            // propagate password data.
            if (utils.isPresent(path.join(absfile, '.key'))) {
              fsExtra.removeSync(path.join(absfile, '.key'));
            }
          }
          else {
            fsExtra.copySync(absSourceFile, absfile);
            // Copy the thumb if it exists.
            if (sourceThumbExists) {
              fsExtra.copySync(absSourceThumb, absthumb);
            }
          }
        }
        catch (e) {
          utils.errorExit('Could not copy ' + sourcefile + ' to ' + filename);
        }
      }

      touchUserDir(userdir);
      res.json({saved: '/' + filename});
      return;
    }

    //
    // Enforce the conditional request if present
    //

    if (conditional) {
      if (fs.existsSync(absfile)) {
        mtime = fs.statSync(absfile).mtime.getTime();
        if (mtime > conditional) {
            res.json({error: 'Did not overwrite newer file.',
                      newer: mtime});
            return;
        }
      }
    }

    //
    // Handle the delete case
    //

    if (!data && !(meta && (meta.html || meta.css))) {
        //if (!req.body.hasOwnProperty('data')) {
        //utils.errorExit('Missing data= form field argument.');
        //}

      if (fs.existsSync(absthumb)) {
        tryToRemoveSync(absthumb);
      }

      if (fs.existsSync(absfile)) {
        tryToRemoveSync(absfile);
      }

      if (userdir != absfile) {
        touchUserDir(userdir);
      }

      res.json({'deleted' : filename});
      return;
    }

    // Validate data
    if (data.length > 1024 * 1024) {
      utils.errorExit('Data too large.');
    }

    //
    // Finally handle the create/replace case
    //

    if (!fs.existsSync(path.dirname(absfile)) ||
        !fs.statSync(path.dirname(absfile)).isDirectory()) {
      checkReservedUser(user, app);
      tryToMkdirsSync(absfile);
    }

    var content = filemeta.printMetaString(data, meta);
    fd = tryToWriteFileSync(absfile, content);

    // If thumbnail exists and it is valid, remove the data url header.
    // and then save as png.
    if (thumbnail && /^data:image\/png;base64,/.test(thumbnail)) {
      var base64data = thumbnail.replace(/^data:image\/png;base64,/, '');
      tryToMkdirsSync(absthumb);
      tryToWriteFileSync(absthumb, base64data, { encoding: 'base64' });
    }

    var statObj = fs.statSync(absfile);
    touchUserDir(userdir);
    res.json({
      saved: '/' + filename,
      mtime: statObj.mtime.getTime(),
      size: statObj.size
    });
  }
  catch (e) {
    if (e instanceof utils.ImmediateReturnError) {
      res.json(e.jsonObj);
    }
    else {
      throw e;
    }
  }
}

function tryToWriteFileSync(absfilename, data, options) {
  try {
    return fs.writeFileSync(absfilename, data, options);
  } catch (e) {
    utils.errorExit('Error writing file: '  absfilename);
  }
}

function tryToMkdirsSync(absfilename) {
  try {
    fsExtra.mkdirsSync(path.dirname(absfilename));
  } catch (e) {
    utils.errorExit('Could not create dir: '  path.dirname(absfilename));
  }
}

function tryToRemoveSync(absfilename) {
  try {
    fsExtra.removeSync(absfilename);
  } catch (e) {
    utils.errorExit('Could not remove: '  absfilename);
  }

  try {
    removeDirsSync(path.dirname(absfilename));
  } catch (e) { }
}

function touchUserDir(userdir) {
  try {
    var now = new Date;
    fs.utimesSync(userdir, now, now);
  }
  catch (e) { }
}

function filenameuser(filename) {
  var m = filename.match(/^([\w][\w\.\-]*)(?:\/.*)?$/);

  return (m) ? m[1] : null;
}

function removeDirsSync(dirStart) {
  for (var dir = dirStart; ; dir = path.dirname(dir)) {
    try {
      fs.rmdirSync(dir);
    } catch (e) {
      // Failed to remove dir, assume not empty.
      break;
    }
  }
}

function isValidKey(user, key, app) {
  //
  // keydir is the directory containing the hashed user password.
  // It's a subdir off the user home directory called '.key'.
  // Contents of this directory are files that are named
  // with the hashed user password
  //

  var keydir = utils.getKeyDir(user, app);
  var statObj = null;

  if (!utils.isPresent(keydir, 'dir')) {
    return true;
  }

  // Now we know its a directory
  var keys = fs.readdirSync(keydir);
  if (!keys || keys.length == 0) {
    // No key files, must mean no password for user.
    // So assume that this is valid.
    return true;
  }

  if (key) {
    for (var i = 0; i < keys.length; i++) {
      //
      // Password files are named with 'k' + the hashed password.
      // See doSetKey() for implementation.  So check the substring
      // offset with 1 to ignore the starting 'k'
      //
      if (key.indexOf(keys[i].substring(1)) == 0) {
        return true;
      }
    }
  }
}

//
// Create a file with the hashed user password in the
// user key dir.  This is called when the user sets or changes
// their password.
//
function doSetKey(user, oldkey, newkey, res, app) {
  if (oldkey == newkey) {
    return;
  }

  var keydir = utils.getKeyDir(user, app);

  try {
    // Create directory if not present
    if (!fs.existsSync(keydir)) {
      checkReservedUser(user, app);
      fsExtra.mkdirsSync(keydir);
    }

    if (oldkey) {
      //
      // Delete old password file if present
      //

      var keys = fs.readdirSync(keydir);

      for (var i = 0; i < keys.length; i++) {
        if (oldkey.indexOf(keys[i].substring(1)) == 0) {
          fs.unlink(path.join(keydir, keys[i]));
        }
      }
    }

    if (newkey) {
      // Now create new password file
      keyfile = path.join(keydir, 'k' + newkey);
      fs.closeSync(fs.openSync(keyfile, 'w'));
    }
  }
  catch (e) {
    if (e instanceof utils.ImmediateReturnError) {
      throw e;
    }
    utils.errorExit('Could not set password.');
  }
}

function letterComplexity(s) {
  var maxcount = 0, uniqcount = 0, dupcount = 0, last = null, count = {}, j, c;
  for (j = 0; j < s.length; ++j) {
    c = s.charAt(j);
    if (!(c in count)) {
      uniqcount += 1;
      count[c] = 0;
    }
    count[c] += 1;
    maxcount = Math.max(count[c], maxcount);
    if (c == last) { dupcount += 1; }
    last = c;
  }
  return uniqcount && (uniqcount / (maxcount + dupcount));
}

function checkReservedUser(user, app) {
  var datadirAbs = path.resolve(app.locals.config.dirs.datadir);

  if (fs.existsSync(path.join(datadirAbs, user))) {
    return;
  }

  if (user != user.toLowerCase()) {
    utils.errorExit('Username should be lowercase.');
  }

  var normalized = user.toLowerCase();
  if (fs.existsSync(path.join(datadirAbs, normalized))) {
    utils.errorExit('Username is reserved.');
  }

  // Prohibit low-complexity usernames (like "aaaaaa")
  if (letterComplexity(normalized) <= 1) {
    utils.errorExit('Username is reserved.');
  }

  // Also check possible variations of badwords
  var normalizedi = translate(normalized, '013456789', 'oieasbtbg');
  if (normalized != normalizedi &&
      fs.existsSync(path.join(datadirAbs, normalizedi))) {
    utils.errorExit('Username is reserved.');
  }

  var normalizedl = translate(normalized, '013456789', 'oleasbtbg');
  if (normalizedl != normalized &&
      fs.existsSync(path.join(datadirAbs, normalizedl))) {
    utils.errorExit('Username is reserved.');
  }

  var checkwords = [normalized, normalizedi, normalizedl];
  var badwords =
      fs.readFileSync(path.join(__dirname, 'bad-words.txt'), 'utf8').
          split(/\n/);
  var badsubstrings =
      fs.readFileSync(path.join(__dirname, 'bad-substrings.txt'), 'utf8').
          split(/\n/);

  for (var i = 0; i < checkwords.length; i++) {
    for (var j = 0; j < badwords.length; j++) {
      if (badwords[j].length > 0 && checkwords[i] == badwords[j]) {
        utils.errorExit('Username is reserved.');
      }
    }
    for (var j = 0; j < badsubstrings.length; j++) {
      if (badsubstrings[j].length > 0 &&
          checkwords[i].indexOf(badsubstrings[j]) != -1) {
        utils.errorExit('Username is reserved.');
      }
    }
  }
}

function translate(source, from, to) {
  var fromArr = from.split('');
  var toArr = to.split('');
  var copy = new String(source);

  if (fromArr.length != toArr.length) {
    utils.errorExit('Uh oh, parameters to translate are incorrect.');
  }

  for (var i = 0; i < fromArr.length; i++) {
    var x = copy.indexOf(fromArr[i]);
    if (x != -1) {
      // Match found, so replace it
      copy[x] = toArr[i];
    }
  }
  return copy;
}
<|endoftext|>