<commit_before>// jQuery to collapse the navbar on scroll
$(window).scroll(function() {
    if ($(".navbar").offset().top > 50) {
      if(!$('.navbar-fixed-top').hasClass('top-nav-collapse')) {
        $(".navbar-fixed-top").addClass("top-nav-collapse");
      }
    } else {
      if($('.navbar-fixed-top').hasClass('top-nav-collapse')) {
        $(".navbar-fixed-top").removeClass("top-nav-collapse");
      }
    }
});

// jQuery for page scrolling feature - requires jQuery Easing plugin
$.getJSON('/sources', function(sources) {
  console.log(sources);
  var engine = new Bloodhound({
    name: 'sources',
    local: sources.map(function (item) {
      return {name: item.val};
    }),
    datumTokenizer: function(d) {
      return Bloodhound.tokenizers.whitespace(d.name);
    },
    queryTokenizer: Bloodhound.tokenizers.whitespace
  });

  engine.initialize();
  $('#typeahead').tagsinput({
    typeaheadjs: {
      name: 'sources',
      displayKey: 'name',
      valueKey: 'name',
      source: engine.ttAdapter()
    },
    freeInput: false
  });
  engine.local.forEach(function(tag) {
    $('input:eq(0)').tagsinput('add', tag.name);
  });

});

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function() {
    $('.navbar-toggle:visible').click();
});

started = false;

var goToSection = function () {
  $('html, body').stop().animate({
      scrollTop: $('#' + Backbone.history.fragment).offset().top
  }, 500, 'easeInOutExpo');
};

var AppRouter = Backbone.Router.extend ({
  routes: {
    '': function () {
      started = true;
      // Intro route
      $('section').hide();
      $('#option-proximity').hide();
      $('#option-occurrence').hide();
      $('.navbar-nav li').hide();
      $('.navbar-nav li:eq(0)').show();
      $('.selected').removeClass('selected');
    },
    'sources': function () {
      if(!started) {this.navigate('',{trigger:true});return;}

      var type = $('header .selected').parent().data('code');

      // Sources route

      if(type === "proximity") {
        $('#option-proximity').show();
      }
      if(type === "occurrence") {
        $('#option-occurrence').show();
      }

      $('section#sources').show();
      $('.navbar-nav li').hide();
      $('.navbar-nav li:eq(0)').show();
      $('.navbar-nav li:eq(1)').show();
      goToSection();
    },
    'graphs': function () {
      if(!started) {this.navigate('',{trigger:true});return;}
      var items = $('input:eq(0)').tagsinput('items');
      console.log(items);
      if(items.length === 0) {
        this.navigate('sources',{trigger: true});
        return;
      }
      var type = $('header .selected').parent().data('code');
      var words = $('#option-proximity input').map(function(){return $(this).val();});
      if(type === "proximity" && (words[0] === "" || words[1] === "")) {
        this.navigate('sources',{trigger: true});
      }
      if(type === "occurrence" && $('#option-occurrence input').val() === "") {
        this.navigate('sources',{trigger: true});
      }
      // Graphs route
      $('section#graphs').show();
      $('.navbar-nav li:eq(2)').show();
      var sourceList = items.map(function (str) {
        return str.replace(/\s/g, '');
      }).join('?');

      $('#graph-location').html('');
      switch (type) {
        case 'freq':
          $.getJSON('/static/sample_freq_data.json', function (data) {
            frequency(data, items);
          });
          break;
        case 'occurrence':
          $.getJSON('/api?sources=' + sourceList + '&type=unique_freq&input1=' + escape($('#option-occurrence input').val()), function (data) {
            occurrence(data);
          });
          break;
        case 'sentiment':
          $.getJSON('/api?sources=' + sourceList + '&type=sentiment', function (data) {
            console.log(data);
            sentiment(data);
          });
          break;
        case 'readability':
          $.getJSON('/api?sources=' + sourceList + '&type=readability', function (data) {
            readability(data);
          });
          break;
        case 'proximity':
          $.getJSON('/api?sources=' + sourceList + '&type=proximity&input1=' + words[0] + '&input2=' + words[1], function (data) {
            console.log(data);
            _(data).each(function (value, key) {
              proximity([
                {caption: "First word alone", number: value[0]},
                {caption: "Second word alone", number: value[1]},
                {caption: "Together", number: value[2]},
              ], key);
            });
          });
          break;
        default:
          this.navigate('', {trigger: true});

      }
      goToSection();
    },
    '*x': function () {
      this.navigate('', {trigger:true});
    }
  }
});

$(document).ready(function () {
  var appRouter = new AppRouter();
  Backbone.history.start();

  $('.intro .list-group li').click(function () {
    $('.intro .list-group li').removeClass('selected');
    $(this).addClass('selected');
  })
});
<commit_msg>hackiness overload<commit_after>// jQuery to collapse the navbar on scroll
$(window).scroll(function() {
    if ($(".navbar").offset().top > 50) {
      if(!$('.navbar-fixed-top').hasClass('top-nav-collapse')) {
        $(".navbar-fixed-top").addClass("top-nav-collapse");
      }
    } else {
      if($('.navbar-fixed-top').hasClass('top-nav-collapse')) {
        $(".navbar-fixed-top").removeClass("top-nav-collapse");
      }
    }
});

// jQuery for page scrolling feature - requires jQuery Easing plugin
$.getJSON('/sources', function(sources) {
  sources = [
      {
      val: "Huffington Post"
      },
      {
      val: "CNN"
      },
      {
      val: "FOX News"
      },
      {
      val: "BBC"
      }
    ];
  console.log(sources);
  var engine = new Bloodhound({
    name: 'sources',
    local: sources.map(function (item) {
      return {name: item.val};
    }),
    datumTokenizer: function(d) {
      return Bloodhound.tokenizers.whitespace(d.name);
    },
    queryTokenizer: Bloodhound.tokenizers.whitespace
  });

  engine.initialize();
  $('#typeahead').tagsinput({
    typeaheadjs: {
      name: 'sources',
      displayKey: 'name',
      valueKey: 'name',
      source: engine.ttAdapter()
    },
    freeInput: false
  });
  engine.local.forEach(function(tag) {
    $('input:eq(0)').tagsinput('add', tag.name);
  });

});

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function() {
    $('.navbar-toggle:visible').click();
});

started = false;

var goToSection = function () {
  $('html, body').stop().animate({
      scrollTop: $('#' + Backbone.history.fragment).offset().top
  }, 500, 'easeInOutExpo');
};

var AppRouter = Backbone.Router.extend ({
  routes: {
    '': function () {
      started = true;
      // Intro route
      $('section').hide();
      $('#option-proximity').hide();
      $('#option-occurrence').hide();
      $('.navbar-nav li').hide();
      $('.navbar-nav li:eq(0)').show();
      $('.selected').removeClass('selected');
    },
    'sources': function () {
      if(!started) {this.navigate('',{trigger:true});return;}

      var type = $('header .selected').parent().data('code');

      // Sources route

      if(type === "proximity") {
        $('#option-proximity').show();
      }
      if(type === "occurrence") {
        $('#option-occurrence').show();
      }

      $('section#sources').show();
      $('.navbar-nav li').hide();
      $('.navbar-nav li:eq(0)').show();
      $('.navbar-nav li:eq(1)').show();
      goToSection();
    },
    'graphs': function () {
      if(!started) {this.navigate('',{trigger:true});return;}
      var items = $('input:eq(0)').tagsinput('items');
      console.log(items);
      if(items.length === 0) {
        this.navigate('sources',{trigger: true});
        return;
      }
      var type = $('header .selected').parent().data('code');
      var words = $('#option-proximity input').map(function(){return $(this).val();});
      if(type === "proximity" && (words[0] === "" || words[1] === "")) {
        this.navigate('sources',{trigger: true});
      }
      if(type === "occurrence" && $('#option-occurrence input').val() === "") {
        this.navigate('sources',{trigger: true});
      }
      // Graphs route
      $('section#graphs').show();
      $('.navbar-nav li:eq(2)').show();
      var sourceList = items.map(function (str) {
        return str.replace(/\s/g, '');
      }).join('?');

      $('#graph-location').html('');
      switch (type) {
        case 'freq':
          $.getJSON('/static/sample_freq_data.json', function (data) {
            frequency(data, items);
          });
          break;
        case 'occurrence':
          $.getJSON('/api?sources=' + sourceList + '&type=unique_freq&input1=' + escape($('#option-occurrence input').val()), function (data) {
            occurrence(data);
          });
          break;
        case 'sentiment':
          $.getJSON('/api?sources=' + sourceList + '&type=sentiment', function (data) {
            console.log(data);
            sentiment(data);
          });
          break;
        case 'readability':
          $.getJSON('/api?sources=' + sourceList + '&type=readability', function (data) {
            readability(data);
          });
          break;
        case 'proximity':
          $.getJSON('/api?sources=' + sourceList + '&type=proximity&input1=' + words[0] + '&input2=' + words[1], function (data) {
            console.log(data);
            _(data).each(function (value, key) {
              proximity([
                {caption: "First word alone", number: value[0]},
                {caption: "Second word alone", number: value[1]},
                {caption: "Together", number: value[2]},
              ], key);
            });
          });
          break;
        default:
          this.navigate('', {trigger: true});

      }
      goToSection();
    },
    '*x': function () {
      this.navigate('', {trigger:true});
    }
  }
});

$(document).ready(function () {
  var appRouter = new AppRouter();
  Backbone.history.start();

  $('.intro .list-group li').click(function () {
    $('.intro .list-group li').removeClass('selected');
    $(this).addClass('selected');
  })
});
<|endoftext|>


<commit_before>/*global define:false */
/*
 * =======================
 * jQuery Timer Plugin
 * =======================
 * Start/Stop/Resume a time in any HTML element
 */

(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define(['jquery'], factory);
	} else {
		factory(root.jQuery);
	}
}(this, function($) {
	// PRIVATE
	var intr,
		totalSeconds = 0,
		isTimerRunning = false,
		startTime,
		duration = null,
		options = {
			seconds: 0,									// default seconds value to start timer from
			editable: false,							// this will let users make changes to the time
			restart: false,								// this will enable stop or continue after a timer callback
			duration: null,								// duration to run callback after
			// callback to run after elapsed duration
			callback: function() {
				alert('Time up!');
				stopTimerInterval();
			},
			repeat: false,								// this will repeat callback every n times duration is elapsed
			countdown: false,							// if true, this will render the timer as a countdown if duration > 0
			format: null,								// this sets the format in which the time will be printed
			updateFrequency: 1000						// How often should timer display update (default 500ms)
		},
		$el,
		display = 'html',	// to be used as $el.html in case of div and $el.val in case of input type text
		// Constants for various states of the timer
		TIMER_STOPPED = 'stopped',
		TIMER_RUNNING = 'running',
		TIMER_PAUSED = 'paused';

	/**
	 * Common function to start or resume a timer interval
	 */
	function startTimerInterval() {
		intr = setInterval(incrementSeconds, options.updateFrequency);
		isTimerRunning = true;
	}

	/**
	 * Common function to stop timer interval
	 */
	function stopTimerInterval() {
		clearInterval(intr);
		isTimerRunning = false;
	}

	/**
	 * Increment total seconds by subtracting startTime from the current unix timestamp in seconds
	 * and call render to display pretty time
	 */
	function incrementSeconds() {
		totalSeconds = getUnixSeconds() - startTime;
		render();

		// Check if totalSeconds is equal to duration if any
		if (duration && totalSeconds % duration === 0) {
			// Run the default callback
			options.callback();

			// If 'repeat' is not requested then disable the duration
			if (!options.repeat) {
				duration = options.duration = null;
			}

			// If this is a countdown, then end it as duration has completed
			if (options.countdown) {
				options.countdown = false;
			}
		}
	}

	/**
	 * Render pretty time
	 */
	function render() {
		var sec = totalSeconds;

		if (options.countdown && duration > 0) {
			sec = duration - totalSeconds;
		}

		$el[display](secondsToTime(sec));
		$el.data('seconds', sec);
	}

	/**
	 * Method to make timer field editable
	 * This method hard binds focus & blur events to pause & resume
	 * and recognizes built-in pretty time (for eg 12 sec OR 3:34 min)
	 * It won't recognize user created formats.
	 * Users may not always want this hard bound. In such a case,
	 * do not use the editable property. Instead bind custom functions
	 * to blur and focus.
	 */
	function makeEditable() {
		$el.on('focus', function() {
			pauseTimer();
		});

		$el.on('blur', function() {
			// eg. 12 sec 3:34 min 12:30 min
			var val = $el[display](), valArr;

			if (val.indexOf('sec') > 0) {
				// sec
				totalSeconds = Number(val.replace(/\ssec/g, ''));
			} else if (val.indexOf('min') > 0) {
				// min
				val = val.replace(/\smin/g, '');
				valArr = val.split(':');
				totalSeconds = Number(valArr[0] * 60) + Number(valArr[1]);
			} else if (val.match(/\d{1,2}:\d{2}:\d{2}/)) {
				// hrs
				valArr = val.split(':');
				totalSeconds = Number(valArr[0] * 3600) + Number(valArr[1] * 60) + Number(valArr[2]);
			}

			resumeTimer();
		});
	}

	/**
	 * Get the current unix timestamp in seconds
	 * @return {Number} [unix timestamp in seconds]
	 */
	function getUnixSeconds() {
		return Math.round(new Date().getTime() / 1000);
	}

	/**
	 * Convert a number of seconds into an object of hours, minutes and seconds
	 * @param  {Number} sec [Number of seconds]
	 * @return {Object}     [An object with hours, minutes and seconds representation of the given seconds]
	 */
	function sec2TimeObj(sec) {
		var hours = 0, minutes = Math.floor(sec / 60), seconds;

		// Hours
		if (sec >= 3600) {
			hours = Math.floor(sec / 3600);
		}

		// Minutes
		if (sec >= 3600) {
			minutes = Math.floor(sec % 3600 / 60);
		}
		// Prepend 0 to minutes under 10
		if (minutes < 10 && hours > 0) {
			minutes = '0' + minutes;
		}
		// Seconds
		seconds = sec % 60;
		// Prepend 0 to seconds under 10
		if (seconds < 10 && (minutes > 0 || hours > 0)) {
			seconds = '0' + seconds;
		}

		return {
			hours: hours,
			minutes: minutes,
			seconds: seconds
		};
	}

	/**
	 * Convert the given seconds to an object made up of hours, minutes and seconds and return a pretty display
	 * @param  {Number} sec [Second to display as pretty time]
	 * @return {String}     [Pretty time]
	 */
	function secondsToTime(sec) {
		var time = '',
			timeObj = sec2TimeObj(sec);

		if (options.format) {
			var formatDef = [
				{identifier: '%h', value: timeObj.hours, pad: false},
				{identifier: '%m', value: timeObj.minutes, pad: false},
				{identifier: '%s', value: timeObj.seconds, pad: false},
				{identifier: '%H', value: parseInt(timeObj.hours), pad: true},
				{identifier: '%M', value: parseInt(timeObj.minutes), pad: true},
				{identifier: '%S', value: parseInt(timeObj.seconds), pad: true}
			];
			time = options.format;

			formatDef.forEach(function(format) {
				time = time.replace(
					new RegExp(format.identifier.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'),
					(format.pad) ? ((format.value < 10) ? '0' + format.value : format.value) : format.value
				);
			});
		} else {
			if (timeObj.hours) {
				time = timeObj.hours + ':' + timeObj.minutes + ':' + timeObj.seconds;
			} else {
				if (timeObj.minutes) {
					time = timeObj.minutes + ':' + timeObj.seconds + ' min';
				} else {
					time = timeObj.seconds + ' sec';
				}
			}
		}
		return time;
	}

	/**
	 * Convert a string time like 5m30s to seconds
	 * If a number (eg 300) is provided, then return as is
	 * @param  {Number|String} time [The human time to convert to seconds]
	 * @return {Number}      [Number of seconds]
	 */
	function timeToSeconds(time) {
		// In case the passed arg is a number, then use that as number of seconds
		if (!isNaN(Number(time))) {
			return time;
		}

		var hMatch = time.match(/\d{1,2}h/),
			mMatch = time.match(/\d{1,2}m/),
			sMatch = time.match(/\d{1,2}s/),
			seconds = 0;

		time = time.toLowerCase();

		// @todo: throw an error in case of faulty time value like 5m61s or 61m

		if (hMatch) {
			seconds += Number(hMatch[0].replace('h', '')) * 3600;
		}

		if (mMatch) {
			seconds += Number(mMatch[0].replace('m', '')) * 60;
		}

		if (sMatch) {
			seconds += Number(sMatch[0].replace('s', ''));
		}

		return seconds;
	}

	// TIMER INTERFACE
	function startTimer() {
		if (!isTimerRunning) {
			render();
			startTimerInterval();
			$el.data('state', TIMER_RUNNING);
		}
	}

	function pauseTimer() {
		if (isTimerRunning) {
			stopTimerInterval();
			$el.data('state', TIMER_PAUSED);
		}
	}

	function resumeTimer() {
		if (!isTimerRunning) {
			startTime = getUnixSeconds() - totalSeconds;
			startTimerInterval();
			$el.data('state', TIMER_RUNNING);
		}
	}

	function resetTimer() {
		startTime = getUnixSeconds();
		totalSeconds = 0;
		$el.data('seconds', totalSeconds);
		$el.data('state', TIMER_STOPPED);
		duration = options.duration;
	}

	function removeTimer() {
		stopTimerInterval();
		$el.data('plugin_' + pluginName, null);
		$el.data('seconds', null);
		$el.data('state', null);
		$el[display]('');
	}

	// TIMER PROTOTYPE
	var Timer = function(element, userOptions) {
		var elementType;

		options = $.extend(options, userOptions);
		$el = $(element);

		// Setup total seconds from options.seconds (if any)
		totalSeconds = options.seconds;

		// Setup start time if seconds were provided
		startTime = getUnixSeconds() - totalSeconds;

		$el.data('seconds', totalSeconds);
		$el.data('state', TIMER_STOPPED);

		// Check if this is a input/textarea element or not
		elementType = $el.prop('tagName').toLowerCase();
		if (elementType === 'input' || elementType === 'textarea') {
			display = 'val';
		}

		if (options.duration) {
			duration = options.duration = timeToSeconds(options.duration);
		}

		if (options.editable) {
			makeEditable();
		}

	};

	/**
	 * Initialize the plugin with public methods
	 */
	Timer.prototype = {
		start: function() {
			startTimer();
		},

		pause: function() {
			pauseTimer();
		},

		resume: function() {
			resumeTimer();
		},

		reset: function() {
			resetTimer();
		},

		remove: function() {
			removeTimer();
		}
	};

	// INITIALIZE THE PLUGIN
	var pluginName = 'timer';
	$.fn[pluginName] = function(options) {
		options = options || 'start';

		return this.each(function() {
			/**
			 * Allow the plugin to be initialized on an element only once
			 * This way we can call the plugin's internal function
			 * without having to reinitialize the plugin all over again.
			 */
			if (!($.data(this, 'plugin_' + pluginName) instanceof Timer)) {

				/**
				 * Create a new data attribute on the element to hold the plugin name
				 * This way we can know which plugin(s) is/are initialized on the element later
				 */
				$.data(this, 'plugin_' + pluginName, new Timer(this, options));

			}

			/**
			 * Use the instance of this plugin derived from the data attribute for this element
			 * to conduct whatever action requested as a string parameter.
			 */
			var instance = $.data(this, 'plugin_' + pluginName);

			/**
			 * Provision for calling a function from this plugin
			 * without initializing it all over again
			 */
			if (typeof options === 'string') {
				if (typeof instance[options] === 'function') {
					/*
					Pass in 'instance' to provide for the value of 'this' in the called function
					*/
					instance[options].call(instance);
				}
			}

			/**
			 * Allow passing custom options object
			 */
			if (typeof options === 'object') {
				instance.start.call(instance);
			}
		});
	};

}));
<commit_msg>by rlmumford: Callbacks and initial state options.<commit_after>/*global define:false */
/*
 * =======================
 * jQuery Timer Plugin
 * =======================
 * Start/Stop/Resume a time in any HTML element
 */

(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define(['jquery'], factory);
	} else {
		factory(root.jQuery);
	}
}(this, function($) {
	// PRIVATE
	var intr,
		totalSeconds = 0,
		isTimerRunning = false,
		startTime,
		duration = null,
		options = {
			seconds: 0,									// default seconds value to start timer from
			editable: false,							// this will let users make changes to the time
			restart: false,								// this will enable stop or continue after a timer callback
			duration: null,								// duration to run callback after
			// callback to run after elapsed duration
			callback: function() {
				alert('Time up!');
				stopTimerInterval();
			},
      startTimer: function() {},
      pauseTimer: function() {},
      resumeTimer: function() {},
      resetTimer: function() {},
      removeTimer: function() {},
			repeat: false,								// this will repeat callback every n times duration is elapsed
			countdown: false,							// if true, this will render the timer as a countdown if duration > 0
			format: null,								// this sets the format in which the time will be printed
			updateFrequency: 1000						// How often should timer display update (default 500ms)
		},
		$el,
		display = 'html',	// to be used as $el.html in case of div and $el.val in case of input type text
		// Constants for various states of the timer
		TIMER_STOPPED = 'stopped',
		TIMER_RUNNING = 'running',
		TIMER_PAUSED = 'paused';

	/**
	 * Common function to start or resume a timer interval
	 */
	function startTimerInterval() {
		intr = setInterval(incrementSeconds, options.updateFrequency);
		isTimerRunning = true;
	}

	/**
	 * Common function to stop timer interval
	 */
	function stopTimerInterval() {
		clearInterval(intr);
		isTimerRunning = false;
	}

	/**
	 * Increment total seconds by subtracting startTime from the current unix timestamp in seconds
	 * and call render to display pretty time
	 */
	function incrementSeconds() {
		totalSeconds = getUnixSeconds() - startTime;
		render();

		// Check if totalSeconds is equal to duration if any
		if (duration && totalSeconds % duration === 0) {
			// Run the default callback
			options.callback();

			// If 'repeat' is not requested then disable the duration
			if (!options.repeat) {
				duration = options.duration = null;
			}

			// If this is a countdown, then end it as duration has completed
			if (options.countdown) {
				options.countdown = false;
			}
		}
	}

	/**
	 * Render pretty time
	 */
	function render() {
		var sec = totalSeconds;

		if (options.countdown && duration > 0) {
			sec = duration - totalSeconds;
		}

		$el[display](secondsToTime(sec));
		$el.data('seconds', sec);
	}

	/**
	 * Method to make timer field editable
	 * This method hard binds focus & blur events to pause & resume
	 * and recognizes built-in pretty time (for eg 12 sec OR 3:34 min)
	 * It won't recognize user created formats.
	 * Users may not always want this hard bound. In such a case,
	 * do not use the editable property. Instead bind custom functions
	 * to blur and focus.
	 */
	function makeEditable() {
		$el.on('focus', function() {
			pauseTimer();
		});

		$el.on('blur', function() {
			// eg. 12 sec 3:34 min 12:30 min
			var val = $el[display](), valArr;

			if (val.indexOf('sec') > 0) {
				// sec
				totalSeconds = Number(val.replace(/\ssec/g, ''));
			} else if (val.indexOf('min') > 0) {
				// min
				val = val.replace(/\smin/g, '');
				valArr = val.split(':');
				totalSeconds = Number(valArr[0] * 60) + Number(valArr[1]);
			} else if (val.match(/\d{1,2}:\d{2}:\d{2}/)) {
				// hrs
				valArr = val.split(':');
				totalSeconds = Number(valArr[0] * 3600) + Number(valArr[1] * 60) + Number(valArr[2]);
			}

			resumeTimer();
		});
	}

	/**
	 * Get the current unix timestamp in seconds
	 * @return {Number} [unix timestamp in seconds]
	 */
	function getUnixSeconds() {
		return Math.round(new Date().getTime() / 1000);
	}

	/**
	 * Convert a number of seconds into an object of hours, minutes and seconds
	 * @param  {Number} sec [Number of seconds]
	 * @return {Object}     [An object with hours, minutes and seconds representation of the given seconds]
	 */
	function sec2TimeObj(sec) {
		var hours = 0, minutes = Math.floor(sec / 60), seconds;

		// Hours
		if (sec >= 3600) {
			hours = Math.floor(sec / 3600);
		}

		// Minutes
		if (sec >= 3600) {
			minutes = Math.floor(sec % 3600 / 60);
		}
		// Prepend 0 to minutes under 10
		if (minutes < 10 && hours > 0) {
			minutes = '0' + minutes;
		}
		// Seconds
		seconds = sec % 60;
		// Prepend 0 to seconds under 10
		if (seconds < 10 && (minutes > 0 || hours > 0)) {
			seconds = '0' + seconds;
		}

		return {
			hours: hours,
			minutes: minutes,
			seconds: seconds
		};
	}

	/**
	 * Convert the given seconds to an object made up of hours, minutes and seconds and return a pretty display
	 * @param  {Number} sec [Second to display as pretty time]
	 * @return {String}     [Pretty time]
	 */
	function secondsToTime(sec) {
		var time = '',
			timeObj = sec2TimeObj(sec);

		if (options.format) {
			var formatDef = [
				{identifier: '%h', value: timeObj.hours, pad: false},
				{identifier: '%m', value: timeObj.minutes, pad: false},
				{identifier: '%s', value: timeObj.seconds, pad: false},
				{identifier: '%H', value: parseInt(timeObj.hours), pad: true},
				{identifier: '%M', value: parseInt(timeObj.minutes), pad: true},
				{identifier: '%S', value: parseInt(timeObj.seconds), pad: true}
			];
			time = options.format;

			formatDef.forEach(function(format) {
				time = time.replace(
					new RegExp(format.identifier.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'),
					(format.pad) ? ((format.value < 10) ? '0' + format.value : format.value) : format.value
				);
			});
		} else {
			if (timeObj.hours) {
				time = timeObj.hours + ':' + timeObj.minutes + ':' + timeObj.seconds;
			} else {
				if (timeObj.minutes) {
					time = timeObj.minutes + ':' + timeObj.seconds + ' min';
				} else {
					time = timeObj.seconds + ' sec';
				}
			}
		}
		return time;
	}

	/**
	 * Convert a string time like 5m30s to seconds
	 * If a number (eg 300) is provided, then return as is
	 * @param  {Number|String} time [The human time to convert to seconds]
	 * @return {Number}      [Number of seconds]
	 */
	function timeToSeconds(time) {
		// In case the passed arg is a number, then use that as number of seconds
		if (!isNaN(Number(time))) {
			return time;
		}

		var hMatch = time.match(/\d{1,2}h/),
			mMatch = time.match(/\d{1,2}m/),
			sMatch = time.match(/\d{1,2}s/),
			seconds = 0;

		time = time.toLowerCase();

		// @todo: throw an error in case of faulty time value like 5m61s or 61m

		if (hMatch) {
			seconds += Number(hMatch[0].replace('h', '')) * 3600;
		}

		if (mMatch) {
			seconds += Number(mMatch[0].replace('m', '')) * 60;
		}

		if (sMatch) {
			seconds += Number(sMatch[0].replace('s', ''));
		}

		return seconds;
	}

	// TIMER INTERFACE
	function startTimer() {
		if (!isTimerRunning) {
			render();
			startTimerInterval();
			$el.data('state', TIMER_RUNNING);
      options.startTimer($el);
		}
	}

	function pauseTimer() {
		if (isTimerRunning) {
			stopTimerInterval();
			$el.data('state', TIMER_PAUSED);
      options.pauseTimer($el);
		}
	}

	function resumeTimer() {
		if (!isTimerRunning) {
			startTime = getUnixSeconds() - totalSeconds;
			startTimerInterval();
			$el.data('state', TIMER_RUNNING);
      options.resumeTimer($el);
		}
	}

	function resetTimer() {
    options.resetTimer($el);
		startTime = getUnixSeconds();
		totalSeconds = 0;
		$el.data('seconds', totalSeconds);
		$el.data('state', TIMER_STOPPED);
		duration = options.duration;
	}

	function removeTimer() {
		stopTimerInterval();
    options.removeTimer($el);
		$el.data('plugin_' + pluginName, null);
		$el.data('seconds', null);
		$el.data('state', null);
		$el[display]('');
	}

	// TIMER PROTOTYPE
	var Timer = function(element, userOptions) {
		var elementType;

		options = $.extend(options, userOptions);
		$el = $(element);

		// Setup total seconds from options.seconds (if any)
		totalSeconds = options.seconds;

		// Setup start time if seconds were provided
		startTime = getUnixSeconds() - totalSeconds;

		$el.data('seconds', totalSeconds);
		$el.data('state', TIMER_STOPPED);

		// Check if this is a input/textarea element or not
		elementType = $el.prop('tagName').toLowerCase();
		if (elementType === 'input' || elementType === 'textarea') {
			display = 'val';
		}

		if (options.duration) {
			duration = options.duration = timeToSeconds(options.duration);
		}

		if (options.editable) {
			makeEditable();
		}

	};

	/**
	 * Initialize the plugin with public methods
	 */
	Timer.prototype = {
		start: function() {
			startTimer();
		},

		pause: function() {
			pauseTimer();
		},

		resume: function() {
			resumeTimer();
		},

		reset: function() {
			resetTimer();
		},

		remove: function() {
			removeTimer();
		}
	};

	// INITIALIZE THE PLUGIN
	var pluginName = 'timer';
	$.fn[pluginName] = function(options) {
		options = options || 'start';

		return this.each(function() {
			/**
			 * Allow the plugin to be initialized on an element only once
			 * This way we can call the plugin's internal function
			 * without having to reinitialize the plugin all over again.
			 */
			if (!($.data(this, 'plugin_' + pluginName) instanceof Timer)) {

				/**
				 * Create a new data attribute on the element to hold the plugin name
				 * This way we can know which plugin(s) is/are initialized on the element later
				 */
				$.data(this, 'plugin_' + pluginName, new Timer(this, options));

			}

			/**
			 * Use the instance of this plugin derived from the data attribute for this element
			 * to conduct whatever action requested as a string parameter.
			 */
			var instance = $.data(this, 'plugin_' + pluginName);

			/**
			 * Provision for calling a function from this plugin
			 * without initializing it all over again
			 */
			if (typeof options === 'string') {
				if (typeof instance[options] === 'function') {
					/*
					Pass in 'instance' to provide for the value of 'this' in the called function
					*/
					instance[options].call(instance);
				}
			}

			/**
			 * Allow passing custom options object
			 */
			if (typeof options === 'object') {
        if (options.state == TIMER_RUNNING) {
  				instance.start.call(instance);
        }
        else {
          render();
        }
			}
		});
	};

}));
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');

var levels = require('enb-bem/techs/levels');
var files = require('enb-bem/techs/files');
var mergeFiles = require('enb/techs/file-merge');

var provide = require('enb/techs/file-provider');
var bemdeclToDeps = require('./techs/bemdecl-to-deps');
var bemdeclFromBemjson = require('enb-bem/techs/bemdecl-from-bemjson');
var bemdeclFromDepsByTech = require('enb-bem/techs/bemdecl-from-deps-by-tech');
var mergeBemdecl = require('enb-bem/techs/merge-bemdecl');
var deps = require('enb-bem/techs/deps-old');

var bemhtml = require('enb-bemxjst/techs/bemhtml-old');
var htmlFromBemjson = require('enb-bemxjst/techs/html-from-bemjson');

var css = require('enb-stylus/techs/css-stylus');
var js = require('./techs/borschik-include-js');
var modules = require('enb-modules/techs/prepend-modules');

var borschik = require('enb-borschik/techs/borschik');
var copy = require('enb/techs/file-copy');

var NEED_COVERAGE = process.env.ISTANBUL_COVERAGE;

exports.configure = function (config, options) {
    var pattern = path.join(options.destPath, '*');
    var sourceLevels = [].concat(options.sourceLevels);

    config.nodes(pattern, function (nodeConfig) {
        var sublevel = path.join(nodeConfig.getNodePath(), 'blocks');

        if (fs.existsSync(sublevel)) {
            sourceLevels.push(sublevel);
        }

        // Base techs
        nodeConfig.addTechs([
            [levels, { levels: sourceLevels }]
        ]);

        // Deps
        nodeConfig.addTechs([
            [provide, { target: '?.bemjson.js' }],
            [provide, { target: '?.base.bemdecl.js' }],

            [bemdeclFromBemjson, { target: '?.bemjson.bemdecl.js' }],
            [mergeBemdecl, {
                bemdeclSources: ['?.base.bemdecl.js', '?.bemjson.bemdecl.js'],
                bemdeclTarget: '?.bemdecl.js'
            }],
            [bemdeclToDeps, { source: '?.base.bemdecl.js', target: '?.base.deps.js' }],

            [deps]
        ]);

        // Files
        nodeConfig.addTechs([
            [files, {
                depsTarget: '?.base.deps.js',
                filesTarget: '?.base.files',
                dirsTarget: '?.base.dirs'
            }],
            [files]
        ]);

        // Client BEMHTML
        nodeConfig.addTechs([
            [bemdeclFromDepsByTech, {
                target: '?.js.bemhtml.bemdecl.js',
                sourceTech: 'js',
                destTech: 'bemhtml'
            }],
            [bemdeclFromDepsByTech, {
                target: '?.spec-js.bemhtml.bemdecl.js',
                sourceTech: 'spec.js',
                destTech: 'bemhtml'
            }],
            [mergeBemdecl, {
                bemdeclSources: ['?.js.bemhtml.bemdecl.js', '?.spec-js.bemhtml.bemdecl.js', '?.bemjson.bemdecl.js'],
                bemdeclTarget: '?.bemhtml.bemdecl.js'
            }],

            [deps, {
                depsTarget: '?.bemhtml.deps.js',
                bemdeclTarget: '?.bemhtml.bemdecl.js'
            }],
            [files, {
                depsTarget: '?.bemhtml.deps.js',
                filesTarget: '?.bemhtml.files',
                dirsTarget: '?.bemhtml.dirs'
            }],

            [bemhtml, {
                target: '?.browser.bemhtml.js',
                filesTarget: '?.bemhtml.files',
                devMode: false
            }]
        ]);

        // HTML
        nodeConfig.addTechs([
            [bemhtml, { devMode: false }],
            [htmlFromBemjson]
        ]);

        // Browser JS
        nodeConfig.addTechs([
            [js, {
                sourceSuffixes: options.jsSuffixes,
                target: '?.source.browser.js'
            }],
            [modules, {
                target: '?.browser.js',
                source: '?.source.browser.js'
            }],
            [js, {
                target: '?.pure.spec.js',
                sourceSuffixes: ['spec.js'],
                filesTarget: '?.base.files'
            }],
            [mergeFiles, {
                target: '?.pre.spec.js',
                sources: ['?.browser.js', '?.bemhtml.js', '?.pure.spec.js']
            }]
        ]);

        if (NEED_COVERAGE) {
            nodeConfig.addTechs([
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.coverage.spec.js',
                    tech: 'istanbul',
                    techOptions: {
                        instrumentPaths: options.levels.map(function (level) {
                            return typeof level === 'string' ? level : level.path;
                        })
                    },
                    levels: options.levels,
                    freeze: true,
                    minify: false
                }],
                [copy, {
                    source: '?.coverage.spec.js',
                    target: '?.spec.js'
                }]
            ]);
        } else {
            nodeConfig.addTech(
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.spec.js',
                    freeze: true,
                    minify: false
                }]
            );
        }

        // CSS
        nodeConfig.addTech(css);

        nodeConfig.addTargets([
            '?.spec.js', '?.css', '?.html'
        ]);
    });
};
<commit_msg>Fixed path to `borschik-tech-istanbul`<commit_after>var path = require('path');
var fs = require('fs');

var levels = require('enb-bem/techs/levels');
var files = require('enb-bem/techs/files');
var mergeFiles = require('enb/techs/file-merge');

var provide = require('enb/techs/file-provider');
var bemdeclToDeps = require('./techs/bemdecl-to-deps');
var bemdeclFromBemjson = require('enb-bem/techs/bemdecl-from-bemjson');
var bemdeclFromDepsByTech = require('enb-bem/techs/bemdecl-from-deps-by-tech');
var mergeBemdecl = require('enb-bem/techs/merge-bemdecl');
var deps = require('enb-bem/techs/deps-old');

var bemhtml = require('enb-bemxjst/techs/bemhtml-old');
var htmlFromBemjson = require('enb-bemxjst/techs/html-from-bemjson');

var css = require('enb-stylus/techs/css-stylus');
var js = require('./techs/borschik-include-js');
var modules = require('enb-modules/techs/prepend-modules');

var borschik = require('enb-borschik/techs/borschik');
var copy = require('enb/techs/file-copy');

var NEED_COVERAGE = process.env.ISTANBUL_COVERAGE;
var borschikTechIstanbulPath = path.join(__dirname, '..', 'node_modules', 'borschik-tech-istanbul');

exports.configure = function (config, options) {
    var pattern = path.join(options.destPath, '*');
    var sourceLevels = [].concat(options.sourceLevels);

    config.nodes(pattern, function (nodeConfig) {
        var sublevel = path.join(nodeConfig.getNodePath(), 'blocks');

        if (fs.existsSync(sublevel)) {
            sourceLevels.push(sublevel);
        }

        // Base techs
        nodeConfig.addTechs([
            [levels, { levels: sourceLevels }]
        ]);

        // Deps
        nodeConfig.addTechs([
            [provide, { target: '?.bemjson.js' }],
            [provide, { target: '?.base.bemdecl.js' }],

            [bemdeclFromBemjson, { target: '?.bemjson.bemdecl.js' }],
            [mergeBemdecl, {
                bemdeclSources: ['?.base.bemdecl.js', '?.bemjson.bemdecl.js'],
                bemdeclTarget: '?.bemdecl.js'
            }],
            [bemdeclToDeps, { source: '?.base.bemdecl.js', target: '?.base.deps.js' }],

            [deps]
        ]);

        // Files
        nodeConfig.addTechs([
            [files, {
                depsTarget: '?.base.deps.js',
                filesTarget: '?.base.files',
                dirsTarget: '?.base.dirs'
            }],
            [files]
        ]);

        // Client BEMHTML
        nodeConfig.addTechs([
            [bemdeclFromDepsByTech, {
                target: '?.js.bemhtml.bemdecl.js',
                sourceTech: 'js',
                destTech: 'bemhtml'
            }],
            [bemdeclFromDepsByTech, {
                target: '?.spec-js.bemhtml.bemdecl.js',
                sourceTech: 'spec.js',
                destTech: 'bemhtml'
            }],
            [mergeBemdecl, {
                bemdeclSources: ['?.js.bemhtml.bemdecl.js', '?.spec-js.bemhtml.bemdecl.js', '?.bemjson.bemdecl.js'],
                bemdeclTarget: '?.bemhtml.bemdecl.js'
            }],

            [deps, {
                depsTarget: '?.bemhtml.deps.js',
                bemdeclTarget: '?.bemhtml.bemdecl.js'
            }],
            [files, {
                depsTarget: '?.bemhtml.deps.js',
                filesTarget: '?.bemhtml.files',
                dirsTarget: '?.bemhtml.dirs'
            }],

            [bemhtml, {
                target: '?.browser.bemhtml.js',
                filesTarget: '?.bemhtml.files',
                devMode: false
            }]
        ]);

        // HTML
        nodeConfig.addTechs([
            [bemhtml, { devMode: false }],
            [htmlFromBemjson]
        ]);

        // Browser JS
        nodeConfig.addTechs([
            [js, {
                sourceSuffixes: options.jsSuffixes,
                target: '?.source.browser.js'
            }],
            [modules, {
                target: '?.browser.js',
                source: '?.source.browser.js'
            }],
            [js, {
                target: '?.pure.spec.js',
                sourceSuffixes: ['spec.js'],
                filesTarget: '?.base.files'
            }],
            [mergeFiles, {
                target: '?.pre.spec.js',
                sources: ['?.browser.js', '?.bemhtml.js', '?.pure.spec.js']
            }]
        ]);

        if (NEED_COVERAGE) {
            nodeConfig.addTechs([
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.coverage.spec.js',
                    tech: borschikTechIstanbulPath,
                    techOptions: {
                        instrumentPaths: options.levels.map(function (level) {
                            return typeof level === 'string' ? level : level.path;
                        })
                    },
                    levels: options.levels,
                    freeze: true,
                    minify: false
                }],
                [copy, {
                    source: '?.coverage.spec.js',
                    target: '?.spec.js'
                }]
            ]);
        } else {
            nodeConfig.addTech(
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.spec.js',
                    freeze: true,
                    minify: false
                }]
            );
        }

        // CSS
        nodeConfig.addTech(css);

        nodeConfig.addTargets([
            '?.spec.js', '?.css', '?.html'
        ]);
    });
};
<|endoftext|>


<commit_before>var path = require('path'),
    fs = require('fs'),

    levels = require('enb-bem-techs/techs/levels'),
    files = require('enb-bem-techs/techs/files'),
    mergeFiles = require('enb/techs/file-merge'),

    provide = require('enb/techs/file-provider'),
    bemdeclFromBemjson = require('enb-bem-techs/techs/bemjson-to-bemdecl'),
    bemdeclFromDepsByTech = require('enb-bem-techs/techs/bemdecl-from-deps-by-tech'),
    mergeBemdecl = require('enb-bem-techs/techs/merge-bemdecl'),
    deps = require('enb-bem-techs/techs/deps-old'),

    bemhtml = require('enb-bemxjst/techs/bemhtml-old'),
    htmlFromBemjson = require('enb-bemxjst/techs/html-from-bemjson'),

    css = require('enb-stylus/techs/css-stylus'),
    js = require('./techs/borschik-include-js'),
    modules = require('enb-modules/techs/prepend-modules'),

    borschik = require('enb-borschik/techs/borschik'),
    copy = require('enb/techs/file-copy'),

    NEED_COVERAGE = process.env.ISTANBUL_COVERAGE,
    borschikTechIstanbulPath = path.join(__dirname, '..', 'node_modules', 'borschik-tech-istanbul');

exports.configure = function (config, options) {
    var pattern = path.join(options.destPath, '*'),
        sourceLevels = [].concat(options.sourceLevels);

    config.nodes(pattern, function (nodeConfig) {
        var sublevel = path.join(nodeConfig.getNodePath(), 'blocks');

        if (fs.existsSync(sublevel)) {
            sourceLevels.push(sublevel);
        }

        // Base techs
        nodeConfig.addTechs([
            [levels, { levels: sourceLevels }]
        ]);

        // Deps
        nodeConfig.addTechs([
            [provide, { target: '?.bemjson.js' }],
            [provide, { target: '?.base.bemdecl.js' }],

            [bemdeclFromBemjson, { target: '?.bemjson.bemdecl.js' }],
            [mergeBemdecl, {
                target: '?.bemdecl.js',
                sources: ['?.base.bemdecl.js', '?.bemjson.bemdecl.js']
            }],

            [deps]
        ]);

        // Files
        nodeConfig.addTechs([
            [files, {
                depsFile: '?.base.bemdecl.js',
                depsFormat: 'bemdecl.js',
                filesTarget: '?.base.files',
                dirsTarget: '?.base.dirs'
            }],
            [files]
        ]);

        // Client BEMHTML
        nodeConfig.addTechs([
            [bemdeclFromDepsByTech, {
                target: '?.js.bemhtml.bemdecl.js',
                sourceTech: 'js',
                destTech: 'bemhtml'
            }],
            [bemdeclFromDepsByTech, {
                target: '?.spec-js.bemhtml.bemdecl.js',
                sourceTech: 'spec.js',
                destTech: 'bemhtml'
            }],
            [mergeBemdecl, {
                target: '?.bemhtml.bemdecl.js',
                sources: ['?.js.bemhtml.bemdecl.js', '?.spec-js.bemhtml.bemdecl.js', '?.bemjson.bemdecl.js']
            }],

            [deps, {
                target: '?.bemhtml.deps.js',
                sourceDepsFile: '?.bemhtml.bemdecl.js'
            }],
            [files, {
                depsFile: '?.bemhtml.deps.js',
                filesTarget: '?.bemhtml.files',
                dirsTarget: '?.bemhtml.dirs'
            }],

            [bemhtml, {
                target: '?.browser.bemhtml.js',
                filesTarget: '?.bemhtml.files',
                devMode: false
            }]
        ]);

        // HTML
        nodeConfig.addTechs([
            [bemhtml, { devMode: false }],
            [htmlFromBemjson]
        ]);

        // Browser JS
        nodeConfig.addTechs([
            [js, {
                sourceSuffixes: options.jsSuffixes,
                target: '?.source.browser.js'
            }],
            [modules, {
                target: '?.browser.js',
                source: '?.source.browser.js'
            }],
            [js, {
                target: '?.pure.spec.js',
                sourceSuffixes: ['spec.js'],
                filesTarget: '?.base.files'
            }],
            [mergeFiles, {
                target: '?.pre.spec.js',
                sources: ['?.browser.js', '?.bemhtml.js', '?.pure.spec.js']
            }]
        ]);

        if (NEED_COVERAGE) {
            nodeConfig.addTechs([
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.coverage.spec.js',
                    tech: borschikTechIstanbulPath,
                    techOptions: {
                        instrumentPaths: options.levels.map(function (level) {
                            return typeof level === 'string' ? level : level.path;
                        })
                    },
                    levels: options.levels,
                    freeze: true,
                    minify: false
                }],
                [copy, {
                    source: '?.coverage.spec.js',
                    target: '?.spec.js'
                }]
            ]);
        } else {
            nodeConfig.addTech(
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.spec.js',
                    freeze: true,
                    minify: false
                }]
            );
        }

        // CSS
        nodeConfig.addTech(css);

        nodeConfig.addTargets([
            '?.spec.js', '?.css', '?.html'
        ]);
    });
};
<commit_msg>Fixed bemdecl build: add deps by tech `spec.js -> js`<commit_after>var path = require('path'),
    fs = require('fs'),

    levels = require('enb-bem-techs/techs/levels'),
    files = require('enb-bem-techs/techs/files'),
    mergeFiles = require('enb/techs/file-merge'),

    provide = require('enb/techs/file-provider'),
    bemjsonToBemdecl = require('enb-bem-techs/techs/bemjson-to-bemdecl'),
    depsByTechToBemdecl = require('enb-bem-techs/techs/deps-by-tech-to-bemdecl'),
    mergeBemdecl = require('enb-bem-techs/techs/merge-bemdecl'),
    deps = require('enb-bem-techs/techs/deps-old'),

    bemhtml = require('enb-bemxjst/techs/bemhtml-old'),
    htmlFromBemjson = require('enb-bemxjst/techs/html-from-bemjson'),

    css = require('enb-stylus/techs/css-stylus'),
    js = require('./techs/borschik-include-js'),
    modules = require('enb-modules/techs/prepend-modules'),

    borschik = require('enb-borschik/techs/borschik'),
    copy = require('enb/techs/file-copy'),

    NEED_COVERAGE = process.env.ISTANBUL_COVERAGE,
    borschikTechIstanbulPath = path.join(__dirname, '..', 'node_modules', 'borschik-tech-istanbul');

exports.configure = function (config, options) {
    var pattern = path.join(options.destPath, '*'),
        sourceLevels = [].concat(options.sourceLevels);

    config.nodes(pattern, function (nodeConfig) {
        var sublevel = path.join(nodeConfig.getNodePath(), 'blocks');

        if (fs.existsSync(sublevel)) {
            sourceLevels.push(sublevel);
        }

        // Base techs
        nodeConfig.addTechs([
            [levels, { levels: sourceLevels }]
        ]);

        // Deps
        nodeConfig.addTechs([
            [provide, { target: '?.bemjson.js' }],
            [provide, { target: '?.base.bemdecl.js' }],

            [bemjsonToBemdecl, { target: '?.bemjson.bemdecl.js' }],
            [depsByTechToBemdecl, {
                target: '?.spec-js.bemdecl.js',
                filesTarget: '?.base.files',
                sourceTech: 'spec.js',
                destTech: 'js'
            }],
            [mergeBemdecl, {
                target: '?.bemdecl.js',
                sources: ['?.base.bemdecl.js', '?.bemjson.bemdecl.js', '?.spec-js.bemdecl.js']
            }],

            [deps]
        ]);

        // Files
        nodeConfig.addTechs([
            [files, {
                depsFile: '?.base.bemdecl.js',
                depsFormat: 'bemdecl.js',
                filesTarget: '?.base.files',
                dirsTarget: '?.base.dirs'
            }],
            [files]
        ]);

        // Client BEMHTML
        nodeConfig.addTechs([
            [depsByTechToBemdecl, {
                target: '?.js.bemhtml.bemdecl.js',
                sourceTech: 'js',
                destTech: 'bemhtml'
            }],
            [depsByTechToBemdecl, {
                target: '?.spec-js.bemhtml.bemdecl.js',
                sourceTech: 'spec.js',
                destTech: 'bemhtml'
            }],
            [mergeBemdecl, {
                target: '?.bemhtml.bemdecl.js',
                sources: ['?.js.bemhtml.bemdecl.js', '?.spec-js.bemhtml.bemdecl.js', '?.bemjson.bemdecl.js']
            }],

            [deps, {
                target: '?.bemhtml.deps.js',
                sourceDepsFile: '?.bemhtml.bemdecl.js'
            }],
            [files, {
                depsFile: '?.bemhtml.deps.js',
                filesTarget: '?.bemhtml.files',
                dirsTarget: '?.bemhtml.dirs'
            }],

            [bemhtml, {
                target: '?.browser.bemhtml.js',
                filesTarget: '?.bemhtml.files',
                devMode: false
            }]
        ]);

        // HTML
        nodeConfig.addTechs([
            [bemhtml, { devMode: false }],
            [htmlFromBemjson]
        ]);

        // Browser JS
        nodeConfig.addTechs([
            [js, {
                sourceSuffixes: options.jsSuffixes,
                target: '?.source.browser.js'
            }],
            [modules, {
                target: '?.browser.js',
                source: '?.source.browser.js'
            }],
            [js, {
                target: '?.pure.spec.js',
                sourceSuffixes: ['spec.js'],
                filesTarget: '?.base.files'
            }],
            [mergeFiles, {
                target: '?.pre.spec.js',
                sources: ['?.browser.js', '?.bemhtml.js', '?.pure.spec.js']
            }]
        ]);

        if (NEED_COVERAGE) {
            nodeConfig.addTechs([
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.coverage.spec.js',
                    tech: borschikTechIstanbulPath,
                    techOptions: {
                        instrumentPaths: options.levels.map(function (level) {
                            return typeof level === 'string' ? level : level.path;
                        })
                    },
                    levels: options.levels,
                    freeze: true,
                    minify: false
                }],
                [copy, {
                    source: '?.coverage.spec.js',
                    target: '?.spec.js'
                }]
            ]);
        } else {
            nodeConfig.addTech(
                [borschik, {
                    source: '?.pre.spec.js',
                    target: '?.spec.js',
                    freeze: true,
                    minify: false
                }]
            );
        }

        // CSS
        nodeConfig.addTech(css);

        nodeConfig.addTargets([
            '?.spec.js', '?.css', '?.html'
        ]);
    });
};
<|endoftext|>


<commit_before>(function(root) {

// Import
// ========

var _,
    errors,
    util,
    Chronicle,
    Compound;

if (typeof exports !== 'undefined') {
  _ = require('underscore');
  errors   = require('../util/errors');
  util   = require('../util/util');
  Chronicle = require('../../chronicle');
  Compound = require('./compound');
} else {
  _ = root._;
  errors = root.Substance.errors;
  util = root.Substance.util;
  Chronicle = root.Substance.Chronicle;
  Compound = Chronicle.ot.Compound;
}

var INS = "+";
var DEL = "-";

var TextOperation = function(options) {

  // if this operation should be created using an array
  if (_.isArray(options)) {
    options = {
      type: options[0],
      pos: options[1],
      str: options[2]
    };
  }

  if (options.type === undefined || options.pos === undefined || options.str === undefined) {
    throw new errors.ChronicleError("Illegal argument: insufficient data.");
  }

  // '+' or '-'
  this.type = options.type;

  // the position where to apply the operation
  this.pos = options.pos;

  // the string to delete or insert
  this.str = options.str;

  // sanity checks
  if(!this.isInsert() && !this.isDelete()) {
    throw new errors.ChronicleError("Illegal type.");
  }
  if (!_.isString(this.str)) {
    throw new errors.ChronicleError("Illegal argument: expecting string.");
  }
  if (!_.isNumber(this.pos) && this.pos < 0) {
    throw new errors.ChronicleError("Illegal argument: expecting positive number as pos.");
  }
};

TextOperation.fromJSON = function(data) {

  if (data.type === Compound.TYPE) {
    var ops = [];
    for (var idx = 0; idx < data.ops.length; idx++) {
      ops.push(TextOperation.fromJSON(data.ops[idx]));
    }
    return TextOperation.Compound(ops);

  } else {
    return new TextOperation(data);
  }
};

TextOperation.__prototype__ = function() {

  this.clone = function() {
    return new TextOperation(this);
  };

  this.isNOP = function() {
    return this.str.length === 0;
  };

  this.isInsert = function() {
    return this.type === INS;
  };

  this.isDelete = function() {
    return this.type === DEL;
  };

  this.length = function() {
    return this.str.length;
  };

  this.apply = function(str) {
    if (this.isEmpty()) return str;

    if (this.isInsert()) {
      if (str.length < this.pos) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      return str.slice(0, this.pos) + this.str + str.slice(this.pos);
    }

    else {
      if (str.length < this.pos + this.str.length) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      var del = str.slice(this.pos, this.pos + this.str.length);

      if (del !== this.str) {
        throw new errors.ChronicleError("Provided string is incompatible: expected " + this.str + " at position " + this.pos + ", found " + del);
      }

      return str.slice(0, this.pos) + str.slice(this.pos + this.length());
    }
  };

  this.invert = function() {
    var data = {
      type: this.isInsert() ? '-' : '+',
      pos: this.pos,
      str: this.str
    };
    return new TextOperation(data);
  };

  this.hasConflict = function(other) {
    return TextOperation.hasConflict(this, other);
  };

  this.isEmpty = function() {
    return this.str.length === 0;
  };

  this.toJSON = function() {
    return {
      type: this.type,
      pos: this.pos,
      str: this.str
    };
  };

};
TextOperation.prototype = new TextOperation.__prototype__();

var hasConflict = function(a, b) {

  // Insert vs Insert:
  //
  // Insertions are conflicting iff their insert position is the same.

  if (a.type === INS && b.type === INS)  return (a.pos === b.pos);

  // Delete vs Delete:
  //
  // Deletions are conflicting if their ranges overlap.

  if (a.type === DEL && b.type === DEL) {
    // to have no conflict, either `a` should be after `b` or `b` after `a`, otherwise.
    return !(a.pos >= b.pos + b.str.length || b.pos >= a.pos + a.str.length);
  }

  // Delete vs Insert:
  //
  // A deletion and an insertion are conflicting if the insert position is within the deleted range.

  var del, ins;
  if (a.type === DEL) {
    del = a; ins = b;
  } else {
    del = b; ins = a;
  }

  return (ins.pos >= del.pos && ins.pos < del.pos + del.str.length);
};

// Transforms two Insertions
// --------

function transform_insert_insert(a, b, first) {

  if (a.pos === b.pos) {
    if (first) {
      b.pos += a.str.length;
    } else {
      a.pos += b.str.length;
    }
  }

  else if (a.pos < b.pos) {
    b.pos += a.str.length;
  }

  else {
    a.pos += b.str.length;
  }

}

// Transform two Deletions
// --------
//

function transform_delete_delete(a, b, first) {

  // reduce to a normalized case
  if (a.pos > b.pos) {
    return transform_delete_delete(b, a, !first);
  }

  if (a.pos === b.pos && a.str.length > b.str.length) {
    return transform_delete_delete(b, a, !first);
  }

  // take out overlapping parts
  if (b.pos < a.pos + a.str.length) {
    var s = b.pos - a.pos;
    var s1 = a.str.length - s;
    var s2 = s + b.str.length;

    a.str = a.str.slice(0, s) + a.str.slice(s2);
    b.str = b.str.slice(s1);
    b.pos -= s;
  } else {
    b.pos -= a.str.length;
  }

}

// Transform Insert and Deletion
// --------
//

function transform_insert_delete(a, b) {

  if (a.type === DEL) {
    return transform_insert_delete(b, a);
  }

  // we can assume, that a is an insertion and b is a deletion

  // a is before b
  if (a.pos <= b.pos) {
    b.pos += a.str.length;
  }

  // a is after b
  else if (a.pos >= b.pos + b.str.length) {
    a.pos -= b.str.length;
  }

  // Note: this is a conflict case the user should be noticed about
  // If applied still, the deletion takes precedence
  // a.pos > b.pos && <= b.pos + b.length()
  else {
    var s = a.pos - b.pos;
    b.str = b.str.slice(0, s) + a.str + b.str.slice(s);
    a.str = "";
  }

}

var transform0 = function(a, b, options) {

  options = options || {};

  if (options.check && hasConflict(a, b)) {
    throw Chronicle.mergeConflict(a, b);
  }

  if (!options.inplace) {
    a = util.clone(a);
    b = util.clone(b);
  }

  if (a.type === INS && b.type === INS)  {
    transform_insert_insert(a, b, true);
  }
  else if (a.type === DEL && b.type === DEL) {
    transform_delete_delete(a, b, true);
  }
  else {
    transform_insert_delete(a,b);
  }

  return [a, b];
};

var __apply__ = function(op, array) {
  if (_.isArray(op)) {
    op = new TextOperation(op);
  }
  else if (!(op instanceof TextOperation)) {
    op = TextOperation.fromJSON(op);
  }
  return op.apply(array);
};

TextOperation.transform = Compound.createTransform(transform0);
TextOperation.apply = __apply__;

TextOperation.Insert = function(pos, str) {
  return new TextOperation(["+", pos, str]);
};

TextOperation.Delete = function(pos, str) {
  return new TextOperation(["-", pos, str]);
};

TextOperation.Compound = function(ops) {
  // do not create a Compound if not necessary
  if (ops.length === 1) return ops[0];
  else return new Compound(ops);
};

// Converts from a given a sequnence in the format of Tim's lib
// which is an array of numbers and strings.
// 1. positive number: retain a number of characters
// 2. negative number: delete a string with the given length at the current position
// 3. string: insert the given string at the current position

TextOperation.fromOT = function(str, ops) {

  var atomicOps = []; // atomic ops

  // iterating through the sequence and bookkeeping the position
  // in the source and destination str
  var srcPos = 0,
      dstPos = 0;

  if (!_.isArray(ops)) {
    ops = arguments.slice(1);
  }

  _.each(ops, function(op) {
    if (_.isString(op)) { // insert chars
      atomicOps.push(TextOperation.Insert(dstPos, op));
      dstPos += op.length;
    } else if (op<0) { // delete n chars
      var n = -op;
      atomicOps.push(TextOperation.Delete(dstPos, str.slice(srcPos, srcPos+n)));
      srcPos += n;
    } else { // skip n chars
      srcPos += op;
      dstPos += op;
    }
  });

  return TextOperation.Compound(atomicOps);
};

// A helper class to model Text selections and to provide an easy way
// to bookkeep changes by other applied TextOperations
var Range = function(range) {

  this.start = range.start;
  this.end = range.end;

  // control the behaviour when characters are inserted at the boundary
  // if set to true the range will expand
  this.expand = range.expand || false;
};

// Transforms a given range tuple (offset, length) in-place.
// --------
//

var range_transform = function(range, textOp) {

  // handle compound operations
  if (textOp.type === Compound.TYPE) {
    for (var idx = 0; idx < textOp.ops.length; idx++) {
      var op = textOp.ops[idx];
      range_transform(range, op);
    }
    return;
  }

  var start = range.start;
  var end = start + range.length;

  // Delete
  if (textOp.type === DEL) {
    var pos1 = textOp.pos;
    var pos2 = textOp.pos+textOp.str.length;

    if (pos1 <= start) {
      start -= Math.min(pos2-pos1, start-pos1);
    }
    if (pos1 <= end) {
      end -= Math.min(pos2-pos1, end-pos1);
    }

  } else if (textOp.type === INS) {
    var pos = textOp.pos;
    var l = textOp.str.length;

    if ( (pos < start) ||
         (pos === start && !this.expand) ) {
      start += l;
    }

    if ( (pos < end) ||
         (pos === end && this.expand) ) {
      end += l;
    }
  }

  range.start = start;
  range.length = end - start;
};

Range.__prototype__ = function() {

  this.clone = function() {
    return new Range(this);
  };

  this.toJSON = function() {
    var result = {
      start: this.start,
      length: this.length
    };
    if (this.expand) result.expand = true;
    return result;
  };

  this.transform = function(textOp) {
    range_transform(this.range, textOp);
  };

};
Range.prototype = new Range.__prototype__();

Range.transform = function(range, op) {
  range_transform(range, op);
};

Range.fromJSON = function(data) {
  return new Range(data);
};

TextOperation.Range = Range;

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = TextOperation;
} else {
  Chronicle.ot = Chronicle.ot || {};
  Chronicle.ot.TextOperation = TextOperation;
}

})(this);
<commit_msg>Make expand app specific.<commit_after>(function(root) {

// Import
// ========

var _,
    errors,
    util,
    Chronicle,
    Compound;

if (typeof exports !== 'undefined') {
  _ = require('underscore');
  errors   = require('../util/errors');
  util   = require('../util/util');
  Chronicle = require('../../chronicle');
  Compound = require('./compound');
} else {
  _ = root._;
  errors = root.Substance.errors;
  util = root.Substance.util;
  Chronicle = root.Substance.Chronicle;
  Compound = Chronicle.ot.Compound;
}

var INS = "+";
var DEL = "-";

var TextOperation = function(options) {

  // if this operation should be created using an array
  if (_.isArray(options)) {
    options = {
      type: options[0],
      pos: options[1],
      str: options[2]
    };
  }

  if (options.type === undefined || options.pos === undefined || options.str === undefined) {
    throw new errors.ChronicleError("Illegal argument: insufficient data.");
  }

  // '+' or '-'
  this.type = options.type;

  // the position where to apply the operation
  this.pos = options.pos;

  // the string to delete or insert
  this.str = options.str;

  // sanity checks
  if(!this.isInsert() && !this.isDelete()) {
    throw new errors.ChronicleError("Illegal type.");
  }
  if (!_.isString(this.str)) {
    throw new errors.ChronicleError("Illegal argument: expecting string.");
  }
  if (!_.isNumber(this.pos) && this.pos < 0) {
    throw new errors.ChronicleError("Illegal argument: expecting positive number as pos.");
  }
};

TextOperation.fromJSON = function(data) {

  if (data.type === Compound.TYPE) {
    var ops = [];
    for (var idx = 0; idx < data.ops.length; idx++) {
      ops.push(TextOperation.fromJSON(data.ops[idx]));
    }
    return TextOperation.Compound(ops);

  } else {
    return new TextOperation(data);
  }
};

TextOperation.__prototype__ = function() {

  this.clone = function() {
    return new TextOperation(this);
  };

  this.isNOP = function() {
    return this.str.length === 0;
  };

  this.isInsert = function() {
    return this.type === INS;
  };

  this.isDelete = function() {
    return this.type === DEL;
  };

  this.length = function() {
    return this.str.length;
  };

  this.apply = function(str) {
    if (this.isEmpty()) return str;

    if (this.isInsert()) {
      if (str.length < this.pos) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      return str.slice(0, this.pos) + this.str + str.slice(this.pos);
    }

    else {
      if (str.length < this.pos + this.str.length) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      var del = str.slice(this.pos, this.pos + this.str.length);

      if (del !== this.str) {
        throw new errors.ChronicleError("Provided string is incompatible: expected " + this.str + " at position " + this.pos + ", found " + del);
      }

      return str.slice(0, this.pos) + str.slice(this.pos + this.length());
    }
  };

  this.invert = function() {
    var data = {
      type: this.isInsert() ? '-' : '+',
      pos: this.pos,
      str: this.str
    };
    return new TextOperation(data);
  };

  this.hasConflict = function(other) {
    return TextOperation.hasConflict(this, other);
  };

  this.isEmpty = function() {
    return this.str.length === 0;
  };

  this.toJSON = function() {
    return {
      type: this.type,
      pos: this.pos,
      str: this.str
    };
  };

};
TextOperation.prototype = new TextOperation.__prototype__();

var hasConflict = function(a, b) {

  // Insert vs Insert:
  //
  // Insertions are conflicting iff their insert position is the same.

  if (a.type === INS && b.type === INS)  return (a.pos === b.pos);

  // Delete vs Delete:
  //
  // Deletions are conflicting if their ranges overlap.

  if (a.type === DEL && b.type === DEL) {
    // to have no conflict, either `a` should be after `b` or `b` after `a`, otherwise.
    return !(a.pos >= b.pos + b.str.length || b.pos >= a.pos + a.str.length);
  }

  // Delete vs Insert:
  //
  // A deletion and an insertion are conflicting if the insert position is within the deleted range.

  var del, ins;
  if (a.type === DEL) {
    del = a; ins = b;
  } else {
    del = b; ins = a;
  }

  return (ins.pos >= del.pos && ins.pos < del.pos + del.str.length);
};

// Transforms two Insertions
// --------

function transform_insert_insert(a, b, first) {

  if (a.pos === b.pos) {
    if (first) {
      b.pos += a.str.length;
    } else {
      a.pos += b.str.length;
    }
  }

  else if (a.pos < b.pos) {
    b.pos += a.str.length;
  }

  else {
    a.pos += b.str.length;
  }

}

// Transform two Deletions
// --------
//

function transform_delete_delete(a, b, first) {

  // reduce to a normalized case
  if (a.pos > b.pos) {
    return transform_delete_delete(b, a, !first);
  }

  if (a.pos === b.pos && a.str.length > b.str.length) {
    return transform_delete_delete(b, a, !first);
  }

  // take out overlapping parts
  if (b.pos < a.pos + a.str.length) {
    var s = b.pos - a.pos;
    var s1 = a.str.length - s;
    var s2 = s + b.str.length;

    a.str = a.str.slice(0, s) + a.str.slice(s2);
    b.str = b.str.slice(s1);
    b.pos -= s;
  } else {
    b.pos -= a.str.length;
  }

}

// Transform Insert and Deletion
// --------
//

function transform_insert_delete(a, b) {

  if (a.type === DEL) {
    return transform_insert_delete(b, a);
  }

  // we can assume, that a is an insertion and b is a deletion

  // a is before b
  if (a.pos <= b.pos) {
    b.pos += a.str.length;
  }

  // a is after b
  else if (a.pos >= b.pos + b.str.length) {
    a.pos -= b.str.length;
  }

  // Note: this is a conflict case the user should be noticed about
  // If applied still, the deletion takes precedence
  // a.pos > b.pos && <= b.pos + b.length()
  else {
    var s = a.pos - b.pos;
    b.str = b.str.slice(0, s) + a.str + b.str.slice(s);
    a.str = "";
  }

}

var transform0 = function(a, b, options) {

  options = options || {};

  if (options.check && hasConflict(a, b)) {
    throw Chronicle.mergeConflict(a, b);
  }

  if (!options.inplace) {
    a = util.clone(a);
    b = util.clone(b);
  }

  if (a.type === INS && b.type === INS)  {
    transform_insert_insert(a, b, true);
  }
  else if (a.type === DEL && b.type === DEL) {
    transform_delete_delete(a, b, true);
  }
  else {
    transform_insert_delete(a,b);
  }

  return [a, b];
};

var __apply__ = function(op, array) {
  if (_.isArray(op)) {
    op = new TextOperation(op);
  }
  else if (!(op instanceof TextOperation)) {
    op = TextOperation.fromJSON(op);
  }
  return op.apply(array);
};

TextOperation.transform = Compound.createTransform(transform0);
TextOperation.apply = __apply__;

TextOperation.Insert = function(pos, str) {
  return new TextOperation(["+", pos, str]);
};

TextOperation.Delete = function(pos, str) {
  return new TextOperation(["-", pos, str]);
};

TextOperation.Compound = function(ops) {
  // do not create a Compound if not necessary
  if (ops.length === 1) return ops[0];
  else return new Compound(ops);
};

// Converts from a given a sequnence in the format of Tim's lib
// which is an array of numbers and strings.
// 1. positive number: retain a number of characters
// 2. negative number: delete a string with the given length at the current position
// 3. string: insert the given string at the current position

TextOperation.fromOT = function(str, ops) {

  var atomicOps = []; // atomic ops

  // iterating through the sequence and bookkeeping the position
  // in the source and destination str
  var srcPos = 0,
      dstPos = 0;

  if (!_.isArray(ops)) {
    ops = arguments.slice(1);
  }

  _.each(ops, function(op) {
    if (_.isString(op)) { // insert chars
      atomicOps.push(TextOperation.Insert(dstPos, op));
      dstPos += op.length;
    } else if (op<0) { // delete n chars
      var n = -op;
      atomicOps.push(TextOperation.Delete(dstPos, str.slice(srcPos, srcPos+n)));
      srcPos += n;
    } else { // skip n chars
      srcPos += op;
      dstPos += op;
    }
  });

  return TextOperation.Compound(atomicOps);
};

// A helper class to model Text selections and to provide an easy way
// to bookkeep changes by other applied TextOperations
var Range = function(range) {

  this.start = range.start;
  this.end = range.end;

  // control the behaviour when characters are inserted at the boundary
  // if set to true the range will expand
  // this.expand = range.expand || false;
};

// Transforms a given range tuple (offset, length) in-place.
// --------
//

var range_transform = function(range, textOp, expand) {

  // handle compound operations
  if (textOp.type === Compound.TYPE) {
    for (var idx = 0; idx < textOp.ops.length; idx++) {
      var op = textOp.ops[idx];
      range_transform(range, op);
    }
    return;
  }

  var start = range.start;
  var end = start + range.length;

  // Delete
  if (textOp.type === DEL) {
    var pos1 = textOp.pos;
    var pos2 = textOp.pos+textOp.str.length;

    if (pos1 <= start) {
      start -= Math.min(pos2-pos1, start-pos1);
    }
    if (pos1 <= end) {
      end -= Math.min(pos2-pos1, end-pos1);
    }

  } else if (textOp.type === INS) {
    var pos = textOp.pos;
    var l = textOp.str.length;

    if ( (pos < start) ||
         (pos === start && !expand) ) {
      start += l;
    }

    if ( (pos < end) ||
         (pos === end && expand) ) {
      end += l;
    }
  }

  range.start = start;
  range.length = end - start;
};

Range.__prototype__ = function() {

  this.clone = function() {
    return new Range(this);
  };

  this.toJSON = function() {
    var result = {
      start: this.start,
      length: this.length
    };
    // if (this.expand) result.expand = true;
    return result;
  };

  this.transform = function(textOp, expand) {
    range_transform(this.range, textOp, expand);
  };

};
Range.prototype = new Range.__prototype__();

Range.transform = function(range, op, expand) {
  range_transform(range, op, expand);
};

Range.fromJSON = function(data) {
  return new Range(data);
};

TextOperation.Range = Range;

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = TextOperation;
} else {
  Chronicle.ot = Chronicle.ot || {};
  Chronicle.ot.TextOperation = TextOperation;
}

})(this);
<|endoftext|>