<commit_before>//TODO:
// - expose _extended_ settings (first slide, shuffle, transition, duration, etc.)
// - shuffle slides manually (if needed)
// - load the first and set cover mode
// - store cover mode in settings

// http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
function shadeBlend(p,c0,c1) {
    var n=p<0?p*-1:p,u=Math.round,w=parseInt;
    if(c0.length>7){
        var f=c0.split(","),t=(c1?c1:p<0?"rgb(0,0,0)":"rgb(255,255,255)").split(","),R=w(f[0].slice(4)),G=w(f[1]),B=w(f[2]);
        return "rgb("+(u((w(t[0].slice(4))-R)*n)+R)+","+(u((w(t[1])-G)*n)+G)+","+(u((w(t[2])-B)*n)+B)+")"
    }else{
        var f=w(c0.slice(1),16),t=w((c1?c1:p<0?"#000000":"#FFFFFF").slice(1),16),R1=f>>16,G1=f>>8&0x00FF,B1=f&0x0000FF;
        return "#"+(0x1000000+(u(((t>>16)-R1)*n)+R1)*0x10000+(u(((t>>8&0x00FF)-G1)*n)+G1)*0x100+(u(((t&0x0000FF)-B1)*n)+B1)).toString(16).slice(1)
    }
}

function isMobile() {
	return /(Android|webOS|Phone|iPad|iPod|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
}

function arrayToRGB(arr) {
	return "rgb(" + arr.join(",") + ")";
}

$(document).ready(function() {

	var isDebug = true; // toggle this to turn on / off for debug
	if (isDebug) var debug = console.log.bind(window.console)
	else var debug = function(){};

	var $elmt = $("#content");

	// don't use overlay on mobiles
	var overlay = isMobile() ? false : 'js/vegas/overlays/overlay.png';

	// calc palettes while loading images
	var paletteSize = 4;
	var colorThief = new ColorThief();

	var slides = [
		//{ src: "photo/slide00.jpg" },
		{ src: "photo/slide01.jpg" },
		{ src: "photo/slide02.jpg" },
		{ src: "photo/slide03.jpg" },
		{ src: "photo/slide04.jpg" },
		{ src: "photo/slide06.jpg" },
		{ src: "photo/slide07.jpg" },
		{ src: "photo/slide08.jpg" },
		{ src: "photo/slide09.jpg" },
		{ src: "photo/slide10.jpg" },
		{ src: "photo/slide11.jpg" },
		{ src: "photo/slide12.jpg" },
		{ src: "photo/slide13.jpg" },
		{ src: "photo/slide14.jpg" },
		{ src: "photo/slide15.jpg" },
		{ src: "photo/slide16.jpg" },
	];

	var vegasSettings = {
		delay: 7000,
		timer: true,
		//preload: true,
		overlay: overlay,
		cover: true,
		shuffle: true,
		transition: ['fade2', 'fade'],
		transitionDuration: 3000,
		//animation: 'random',
	};

	// split .site-title h1 initials into spans
	var $author = $('h1.site-title a');
	var withSpanInitials = $author.text().replace((/\b\w/g),"$&");
	$author.html(withSpanInitials);

	// toggle menu
	var menuHidden = true;
	$('.overlay').hide();
	$(".logo").on('click', toggleMenu);
	function toggleMenu() {
		debug("toggle menu");
		$('.logo').toggleClass('menu-open');
		if (menuHidden) {
			$('.pulsate').hide();
			$('.overlay').fadeIn(500);
		} else $('.overlay').fadeOut(500, function() { $('.pulsate').show(); });
		menuHidden = !menuHidden;
	}

	// random photos from flickr
	/*var loremFlickrUrl = "http://loremflickr.com/800/600/world?random=";
	for (i = 0; i < slides.length; i++) {
		slides[i].src = loremFlickrUrl + (i + 1);
	}*/

	function addEventListeners() {

		// next slide
		function next() {
			$elmt.vegas('next');
		}

		// prev slide
		function prev() {
			$elmt.vegas('previous');
		}

		// key navigation
		$(document).on('keydown', function(e) {
			if (e.which == 37) // left
				prev();
			else if (e.which == 39) // right
				next();
			else {
				debug(e.which);
				if (e.which == 32) { // space
					debug("toggle overlay");
					var $overlay = $elmt.find('.vegas-overlay');
					var visible = $overlay.css('display') != 'none';
					$overlay.css('display', (visible ? 'none' : 'inline-block'));
					e.preventDefault();
				}
				if (e.which == 77) { // m
					debug("toggle animation");
					var animation = $elmt.vegas('options', 'animation');
					$elmt.vegas('options', 'animation', animation == 'random' ? null : 'random');
					next();
				}
				if (e.which == 80) { // p
					$elmt.find('#palette').toggle();
				}
				if (e.which == 82) { // r
					debug("restart");
					$elmt.vegas('destroy');
					startGallery(vegasSettings, slides);
				}
				if (e.which == 67) { // c
					vegasSettings.cover = (vegasSettings.cover === 'auto' ? true : 'auto');
					debug("toggle cover mode", vegasSettings.cover, $elmt.vegas('options', 'cover'));
					next();
				}
			}
		});

		// create a simple instance of Hammer
		// by default, it only adds horizontal recognizers
		//delete Hammer.defaults.cssProps.userSelect;
		var hammer = new Hammer($elmt.get(0));
		hammer.get('swipe').set({velocity:.2});
		hammer.on('swipeleft swiperight', function(e) {
			//debug(e.type);
			if (e.type == 'swiperight')
				prev();
			else if (e.type == 'swipeleft')
				next();
		});
	}

	// knuth shuffle
	function shuffle(arr) {
		var rand, temp, i;

		for (i = arr.length - 1; i > 0; i--) {
			rand = Math.floor((i + 1) * Math.random());
			temp = arr[rand];
			arr[rand] = arr[i];
			arr[i] = temp;
		}
		return arr;
	}

	// get cover mode for the specified slide index
	function getCoverModeFor(idx) {
		idx = (idx + slides.length) % slides.length;
		var slide = slides[idx];
		var res = vegasSettings.cover;
		if (vegasSettings.cover === 'auto' && slide && slide.size) {
			var ratio = slide.size.w / slide.size.h;
			res = ratio >= 1;
		}
		debug("get cover for " + idx, slide, res);
		return res;
	}

	// set cover mode for the specified slide index
	function setCoverModeFor(idx) {
		idx = (idx + slides.length) % slides.length;
		var cover = getCoverModeFor(idx);
		var slide = slides[idx];
		$elmt.vegas('options', 'cover', cover);
		debug("set cover for " + idx, slide, cover);
	}

	function onBeforeWalk(idx, img) {
		debug("beforeWalk", idx, img, img.width, img.height);
		if (!slides[idx].size) slides[idx].size = {w:img.width, h:img.height};
		try {
			if (!slides[idx].palette) slides[idx].palette = colorThief.getPalette(img, paletteSize);
		} catch (e) {
			debug(e);
		}
		setCoverModeFor(idx);
	}

	function startGallery(options, slides) {

		debug("start gallery");
		$.extend(options, {slides: slides});

		// vegas
		debug(options);
		$elmt.vegas(options)
		.on('click', function() { 	// fullscreen toggle
			//$elmt.vegas('toggle');
			/*if (screenfull.enabled) {
				screenfull.toggle(this);
			}*/
		});

		$elmt.vegas('options').beforeWalk = onBeforeWalk;

		debug("first slide is ", slides[0]);

		// set colors when changing slide
		$elmt.vegas('options', 'walk', function (idx, slideSettings, fromIdx) {

			debug('walk', idx, slideSettings, fromIdx);
			var slide = slides[idx];

			if (slide.palette) {
				$elmt.find(".vegas-timer .vegas-timer-progress").css("background-color", arrayToRGB(slide.palette[0]));
				$(".site-title span").stop().animate({color: arrayToRGB(slide.palette[2])}, 1500);
				if ($("#palette div").length == 0) {
					for (i = 0; i < slide.palette.length; i++) {
						$("
").appendTo("#palette");
					}
				}
				for (i = 0; i < slide.palette.length; i++) {
					$("#palette div:eq(" + i + ")").stop().animate({backgroundColor: arrayToRGB(slide.palette[i])}, 1500);
				}
			}
		});
	}

	addEventListeners();
	startGallery(vegasSettings, slides);
});<commit_msg>pause/unpause slideshow with enter<commit_after>//TODO:
// - expose _extended_ settings (first slide, shuffle, transition, duration, etc.)
// - shuffle slides manually (if needed)
// - load the first and set cover mode
// - store cover mode in settings

// http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
function shadeBlend(p,c0,c1) {
    var n=p<0?p*-1:p,u=Math.round,w=parseInt;
    if(c0.length>7){
        var f=c0.split(","),t=(c1?c1:p<0?"rgb(0,0,0)":"rgb(255,255,255)").split(","),R=w(f[0].slice(4)),G=w(f[1]),B=w(f[2]);
        return "rgb("+(u((w(t[0].slice(4))-R)*n)+R)+","+(u((w(t[1])-G)*n)+G)+","+(u((w(t[2])-B)*n)+B)+")"
    }else{
        var f=w(c0.slice(1),16),t=w((c1?c1:p<0?"#000000":"#FFFFFF").slice(1),16),R1=f>>16,G1=f>>8&0x00FF,B1=f&0x0000FF;
        return "#"+(0x1000000+(u(((t>>16)-R1)*n)+R1)*0x10000+(u(((t>>8&0x00FF)-G1)*n)+G1)*0x100+(u(((t&0x0000FF)-B1)*n)+B1)).toString(16).slice(1)
    }
}

function isMobile() {
	return /(Android|webOS|Phone|iPad|iPod|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
}

function arrayToRGB(arr) {
	return "rgb(" + arr.join(",") + ")";
}

$(document).ready(function() {

	var isDebug = true; // toggle this to turn on / off for debug
	if (isDebug) var debug = console.log.bind(window.console)
	else var debug = function(){};

	var $elmt = $("#content");

	// don't use overlay on mobiles
	var overlay = isMobile() ? false : 'js/vegas/overlays/overlay.png';

	// calc palettes while loading images
	var paletteSize = 4;
	var colorThief = new ColorThief();

	var slides = [
		{ src: "photo/slide00.jpg" },
		{ src: "photo/slide01.jpg" },
		/*{ src: "photo/slide02.jpg" },
		{ src: "photo/slide03.jpg" },
		{ src: "photo/slide04.jpg" },
		{ src: "photo/slide06.jpg" },
		{ src: "photo/slide07.jpg" },
		{ src: "photo/slide08.jpg" },
		{ src: "photo/slide09.jpg" },
		{ src: "photo/slide10.jpg" },
		{ src: "photo/slide11.jpg" },
		{ src: "photo/slide12.jpg" },
		{ src: "photo/slide13.jpg" },
		{ src: "photo/slide14.jpg" },
		{ src: "photo/slide15.jpg" },
		{ src: "photo/slide16.jpg" },*/
	];

	var vegasSettings = {
		delay: 7000,
		timer: true,
		//preload: true,
		overlay: overlay,
		cover: true,
		shuffle: true,
		transition: ['fade2', 'fade'],
		transitionDuration: 3000,
		//animation: 'random',
	};

	// split .site-title h1 initials into spans
	var $author = $('h1.site-title a');
	var withSpanInitials = $author.text().replace((/\b\w/g),"$&");
	$author.html(withSpanInitials);

	// toggle menu
	var menuHidden = true;
	$('.overlay').hide();
	$(".logo").on('click', toggleMenu);
	function toggleMenu() {
		debug("toggle menu");
		$('.logo').toggleClass('menu-open');
		if (menuHidden) {
			$('.pulsate').hide();
			$('.overlay').fadeIn(500);
		} else $('.overlay').fadeOut(500, function() { $('.pulsate').show(); });
		menuHidden = !menuHidden;
	}

	// random photos from flickr
	/*var loremFlickrUrl = "http://loremflickr.com/800/600/world?random=";
	for (i = 0; i < slides.length; i++) {
		slides[i].src = loremFlickrUrl + (i + 1);
	}*/

	function addEventListeners() {

		// next slide
		function next() {
			$elmt.vegas('next');
		}

		// prev slide
		function prev() {
			$elmt.vegas('previous');
		}

		// key navigation
		$(document).on('keydown', function(e) {
			if (e.which == 37) // left
				prev();
			else if (e.which == 39) // right
				next();
			else {
				debug(e.which);
				if (e.which == 13) { // enter
					debug("pause/unpause");
					$elmt.vegas('toggle');
				}
				if (e.which == 32) { // space
					debug("toggle overlay");
					var $overlay = $elmt.find('.vegas-overlay');
					var visible = $overlay.css('display') != 'none';
					$overlay.css('display', (visible ? 'none' : 'inline-block'));
					e.preventDefault();
				}
				if (e.which == 77) { // m
					debug("toggle animation");
					var animation = $elmt.vegas('options', 'animation');
					$elmt.vegas('options', 'animation', animation == 'random' ? null : 'random');
					next();
				}
				if (e.which == 80) { // p
					$elmt.find('#palette').toggle();
				}
				if (e.which == 82) { // r
					debug("restart");
					$elmt.vegas('destroy');
					startGallery(vegasSettings, slides);
				}
				if (e.which == 67) { // c
					vegasSettings.cover = (vegasSettings.cover === 'auto' ? true : 'auto');
					debug("toggle cover mode", vegasSettings.cover, $elmt.vegas('options', 'cover'));
					next();
				}
			}
		});

		// create a simple instance of Hammer
		// by default, it only adds horizontal recognizers
		//delete Hammer.defaults.cssProps.userSelect;
		var hammer = new Hammer($elmt.get(0));
		hammer.get('swipe').set({velocity:.2});
		hammer.on('swipeleft swiperight', function(e) {
			//debug(e.type);
			if (e.type == 'swiperight')
				prev();
			else if (e.type == 'swipeleft')
				next();
		});
	}

	// knuth shuffle
	function shuffle(arr) {
		var rand, temp, i;

		for (i = arr.length - 1; i > 0; i--) {
			rand = Math.floor((i + 1) * Math.random());
			temp = arr[rand];
			arr[rand] = arr[i];
			arr[i] = temp;
		}
		return arr;
	}

	// get cover mode for the specified slide index
	function getCoverModeFor(idx) {
		idx = (idx + slides.length) % slides.length;
		var slide = slides[idx];
		var res = vegasSettings.cover;
		if (vegasSettings.cover === 'auto' && slide && slide.size) {
			var ratio = slide.size.w / slide.size.h;
			res = ratio >= 1;
		}
		debug("get cover for " + idx, slide, res);
		return res;
	}

	// set cover mode for the specified slide index
	function setCoverModeFor(idx) {
		idx = (idx + slides.length) % slides.length;
		var cover = getCoverModeFor(idx);
		var slide = slides[idx];
		$elmt.vegas('options', 'cover', cover);
		debug("set cover for " + idx, slide, cover);
	}

	function onBeforeWalk(idx, img) {
		debug("beforeWalk", idx, img, img.width, img.height);
		if (!slides[idx].size) slides[idx].size = {w:img.width, h:img.height};
		try {
			if (!slides[idx].palette) slides[idx].palette = colorThief.getPalette(img, paletteSize);
		} catch (e) {
			debug(e);
		}
		setCoverModeFor(idx);
	}

	function startGallery(options, slides) {

		debug("start gallery");
		$.extend(options, {slides: slides});

		// vegas
		debug(options);
		$elmt.vegas(options)
		.on('click', function() { 	// fullscreen toggle
			//$elmt.vegas('toggle');
			/*if (screenfull.enabled) {
				screenfull.toggle(this);
			}*/
		});

		$elmt.vegas('options').beforeWalk = onBeforeWalk;

		debug("first slide is ", slides[0]);

		// set colors when changing slide
		$elmt.vegas('options', 'walk', function (idx, slideSettings, fromIdx) {

			debug('walk', idx, slideSettings, fromIdx);
			var slide = slides[idx];

			if (slide.palette) {
				$elmt.find(".vegas-timer .vegas-timer-progress").css("background-color", arrayToRGB(slide.palette[0]));
				$(".site-title span").stop().animate({color: arrayToRGB(slide.palette[2])}, 1500);
				if ($("#palette div").length == 0) {
					for (i = 0; i < slide.palette.length; i++) {
						$("
").appendTo("#palette");
					}
				}
				for (i = 0; i < slide.palette.length; i++) {
					$("#palette div:eq(" + i + ")").stop().animate({backgroundColor: arrayToRGB(slide.palette[i])}, 1500);
				}
			}
		});
	}

	addEventListeners();
	startGallery(vegasSettings, slides);
});<|endoftext|>


<commit_before>var Browser = {
  debug: 0,
  msgCallbackMissing: 'ERROR: Callback is missing',
  msgUnsupported: 'ERROR: Unsupported browser',

  setIcon: function(path) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setIcon({path: path});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  setTitle: function(title) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setTitle({title: title});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBadgeText: function(callback) {
    if (typeof callback == 'undefined') {
      console.log(this.msgCallbackMissing);
    }
    else {
      if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
        chrome.browserAction.getBadgeText({}, function(badgeText) {
          callback(badgeText);
        });
      }
      else {
        console.log(this.msgUnsupported);
      }
    }
  },

  setBadgeText: function(text) {
    if (typeof text == 'undefined' || text == null || isNaN(Number(text)) || Number(text) <= 0) {
      text = '';
    }
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.browserAction != undefined) {
        text = String(text);
        chrome.browserAction.setBadgeText({text: text});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openTab: function(url) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: true});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openBackgroundTab: function(url) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: false});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBackgroundProcess: function() {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.extension != undefined) {
        return chrome.extension.getBackgroundPage();
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  // Things in item:
  // - feedKey: 'orgx'
  // - title: 'Hello World'
  // - description: 'Good day to you World, how are you today?'
  // - link: 'http://orgx.no/news/helloworld'
  // Optional things in item:
  // - image: 'http://orgx.no/media/helloworld.png'
  // - symbol: 'img/whatever.png'
  // - longStory: true
  createNotification: function(item) {
    // Check required params
    if (!item.feedKey) console.log('ERROR: item.feedKey is required');
    if (!item.title) console.log('ERROR: item.title is required');
    if (!item.description) console.log('ERROR: item.description is required');
    if (!item.link) console.log('ERROR: item.link is required');

    var self = this;
    if (BROWSER == 'Chrome') {
      // Check if browser is active, not "idle" or "locked"
      if (chrome.idle) {
        chrome.idle.queryState(30, function (state) {
          if (state == 'active') {

            // Load affiliation icon if symbol is not provided
            if (!item.symbol)
              item.symbol = Affiliation.org[item.feedKey].symbol;

            // Set options
            var options = {
               type: 'basic',
               iconUrl: item.symbol,
               title: item.title,
               message: item.description,
               priority: 0,
            };

            // We'll show an "image"-type notification if image exists and is not a placeholder
            if (item.image) {
              if (item.image != Affiliation.org[item.feedKey].placeholder) {
                options.type = 'image';
                options.imageUrl = item.image;
              }
            }

            // Shorten messages to fit nicely
            var maxLength = (item.longStory ? 600 : 63);
            if (maxLength < item.description.length) {
              options.message = item.description.substring(0, maxLength) + '...';
            }
            // If basic type is used, we should also provide expandedMessage
            if (options.type == 'basic') {
              options.expandedMessage = item.description;
              var expandedMaxLength = (item.longStory ? 600 : 180);
              if (expandedMaxLength < item.description.length) {
                options.expandedMessage = item.description.substring(0, expandedMaxLength) + '...';
              }
            }

            // Generate random ID
            var id = String(Math.round(Math.random()*100000));

            // Save the link to make the notification clickable. Associating the id with the
            // window object instead of putting it in localStorage makes sure all links are
            // cleared every time the background process restarts, rather than filling up
            // localStorage with links.
            window[id] = item.link;

            // Show the notification
            chrome.notifications.create(id, options, function(notID) {
              if (self.debug) console.log('Succesfully created notification with ID', notID);
            });
            // Chrom(e|ium) on Linux doesn't remove the notification automatically
            var timeout = (item.longStory ? 10000 : 5000);
            setTimeout(function() {
              chrome.notifications.clear(id, function(wasCleared) {
                if (self.debug) console.log('Cleared notification?', wasCleared);
              });
            }, timeout);
          }
          else {
            if (self.debug) console.log('Notification not sent, state was', state);
          }
        });
      }
      else {
        if (self.debug) console.log('ERROR: This version of Chrome does not support chrome.idle');
      }
    }
    else if (BROWSER == 'Opera') {
      // Desktop Notifications not yet available
      if (self.debug) console.log('BROWSER.JS: createNotification, not yet avaliable in Opera');
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  // Event handlers for the various notification events

  registerNotificationListeners: function() {
    if (BROWSER == 'Chrome') {
      window.addEventListener("load", function() {
        chrome.notifications.onClosed.addListener(Browser.notificationClosed);
        chrome.notifications.onClicked.addListener(Browser.notificationClicked);
        chrome.notifications.onButtonClicked.addListener(Browser.notificationBtnClick);
      });
    }
    else if (BROWSER == 'Opera') {
      console.log(this.msgUnsupported);
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  notificationClosed: function(notID, bByUser) {
    if (!DEBUG) {
      if (bByUser) {
        _gaq.push(['_trackEvent', 'notification', 'closeNotification', 'byUser']);
      }
      else {
        _gaq.push(['_trackEvent', 'notification', 'closeNotification', 'automatic']);
      }
    }
  },

  notificationClicked: function(notID) {
    var link = window[notID];
    if (!DEBUG) {
      _gaq.push(['_trackEvent', 'notification', 'clickNotification', link]);
    }
    Browser.openTab(link);
  },

  notificationBtnClick: function(notID, iBtn) {
    if (!DEBUG) {
      _gaq.push(['_trackEvent', 'notification', 'clickNotificationButton', iBtn]);
    }
  },

}
<commit_msg>browser: function for app version<commit_after>var Browser = {
  debug: 0,
  msgCallbackMissing: 'ERROR: Callback is missing',
  msgUnsupported: 'ERROR: Unsupported browser',

  setIcon: function(path) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setIcon({path: path});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  setTitle: function(title) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setTitle({title: title});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBadgeText: function(callback) {
    if (typeof callback == 'undefined') {
      console.log(this.msgCallbackMissing);
    }
    else {
      if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
        chrome.browserAction.getBadgeText({}, function(badgeText) {
          callback(badgeText);
        });
      }
      else {
        console.log(this.msgUnsupported);
      }
    }
  },

  setBadgeText: function(text) {
    if (typeof text == 'undefined' || text == null || isNaN(Number(text)) || Number(text) <= 0) {
      text = '';
    }
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.browserAction != undefined) {
        text = String(text);
        chrome.browserAction.setBadgeText({text: text});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openTab: function(url) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: true});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openBackgroundTab: function(url) {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: false});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBackgroundProcess: function() {
    if (BROWSER == 'Chrome' || BROWSER == 'Opera') {
      if (chrome.extension != undefined) {
        return chrome.extension.getBackgroundPage();
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getAppVersion: function() {
    try {
      if (BROWSER == 'Chrome') {
        return chrome.runtime.getManifest().version + ' @ ' + BROWSER;
      }
      else if (BROWSER == 'Opera') {
        return 'Unknown @ ' + BROWSER // TODO: Implement when available
      }
      else {
        console.log(this.msgUnsupported);
      }
    } catch (err) {
      // Do nothing, we're just checking
    }
    return 'Unknown @ ' + BROWSER
  },

  // Things in item:
  // - feedKey: 'orgx'
  // - title: 'Hello World'
  // - description: 'Good day to you World, how are you today?'
  // - link: 'http://orgx.no/news/helloworld'
  // Optional things in item:
  // - image: 'http://orgx.no/media/helloworld.png'
  // - symbol: 'img/whatever.png'
  // - longStory: true
  createNotification: function(item) {
    // Check required params
    if (!item.feedKey) console.log('ERROR: item.feedKey is required');
    if (!item.title) console.log('ERROR: item.title is required');
    if (!item.description) console.log('ERROR: item.description is required');
    if (!item.link) console.log('ERROR: item.link is required');

    var self = this;
    if (BROWSER == 'Chrome') {
      // Check if browser is active, not "idle" or "locked"
      if (chrome.idle) {
        chrome.idle.queryState(30, function (state) {
          if (state == 'active') {

            // Load affiliation icon if symbol is not provided
            if (!item.symbol)
              item.symbol = Affiliation.org[item.feedKey].symbol;

            // Set options
            var options = {
               type: 'basic',
               iconUrl: item.symbol,
               title: item.title,
               message: item.description,
               priority: 0,
            };

            // We'll show an "image"-type notification if image exists and is not a placeholder
            if (item.image) {
              if (item.image != Affiliation.org[item.feedKey].placeholder) {
                options.type = 'image';
                options.imageUrl = item.image;
              }
            }

            // Shorten messages to fit nicely
            var maxLength = (item.longStory ? 600 : 63);
            if (maxLength < item.description.length) {
              options.message = item.description.substring(0, maxLength) + '...';
            }
            // If basic type is used, we should also provide expandedMessage
            if (options.type == 'basic') {
              options.expandedMessage = item.description;
              var expandedMaxLength = (item.longStory ? 600 : 180);
              if (expandedMaxLength < item.description.length) {
                options.expandedMessage = item.description.substring(0, expandedMaxLength) + '...';
              }
            }

            // Generate random ID
            var id = String(Math.round(Math.random()*100000));

            // Save the link to make the notification clickable. Associating the id with the
            // window object instead of putting it in localStorage makes sure all links are
            // cleared every time the background process restarts, rather than filling up
            // localStorage with links.
            window[id] = item.link;

            // Show the notification
            chrome.notifications.create(id, options, function(notID) {
              if (self.debug) console.log('Succesfully created notification with ID', notID);
            });
            // Chrom(e|ium) on Linux doesn't remove the notification automatically
            var timeout = (item.longStory ? 10000 : 5000);
            setTimeout(function() {
              chrome.notifications.clear(id, function(wasCleared) {
                if (self.debug) console.log('Cleared notification?', wasCleared);
              });
            }, timeout);
          }
          else {
            if (self.debug) console.log('Notification not sent, state was', state);
          }
        });
      }
      else {
        if (self.debug) console.log('ERROR: This version of Chrome does not support chrome.idle');
      }
    }
    else if (BROWSER == 'Opera') {
      // Desktop Notifications not yet available
      if (self.debug) console.log('BROWSER.JS: createNotification, not yet avaliable in Opera');
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  // Event handlers for the various notification events

  registerNotificationListeners: function() {
    if (BROWSER == 'Chrome') {
      window.addEventListener("load", function() {
        chrome.notifications.onClosed.addListener(Browser.notificationClosed);
        chrome.notifications.onClicked.addListener(Browser.notificationClicked);
        chrome.notifications.onButtonClicked.addListener(Browser.notificationBtnClick);
      });
    }
    else if (BROWSER == 'Opera') {
      console.log(this.msgUnsupported);
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  notificationClosed: function(notID, bByUser) {
    if (!DEBUG) {
      if (bByUser) {
        _gaq.push(['_trackEvent', 'notification', 'closeNotification', 'byUser']);
      }
      else {
        _gaq.push(['_trackEvent', 'notification', 'closeNotification', 'automatic']);
      }
    }
  },

  notificationClicked: function(notID) {
    var link = window[notID];
    if (!DEBUG) {
      _gaq.push(['_trackEvent', 'notification', 'clickNotification', link]);
    }
    Browser.openTab(link);
  },

  notificationBtnClick: function(notID, iBtn) {
    if (!DEBUG) {
      _gaq.push(['_trackEvent', 'notification', 'clickNotificationButton', iBtn]);
    }
  },

}
<|endoftext|>


<commit_before>var Gitems = {
  r1: {
    name: 'resource 1',
    type: 'resource',
    total: 0,
    resource: 'r1',
    maxStorage: 10,
    maxStoragePrice: 5,
    maxStorageJump: 1.3,
    maxStoragePriceJump: 1.2
  },
  r2: {
    name: 'resource 2',
    type: 'resource',
    total: 0,
    resource: 'r2',
    maxStorage: 10,
    maxStoragePrice: 5,
    maxStorageJump: 2.0,
    maxStoragePriceJump: 1.2
  },
  i1: {
    name: '1 more clicks on r1',
    type: 'item',
    price: {
      r1: 5
    },
    resource: 'r1',
    modifier: 1,
    priceJump: 1.1,
    owned: 0
  },
  i2: {
    name: '1 more clicks on r2',
    type: 'item',
    price: {
      r2: 5
    },
    resource: 'r2',
    modifier: 1,
    priceJump: 1.1,
    owned: 0
  },
  i3: {
    name: '1 more clicks on r2',
    type: 'item',
    price: {
      r1: 5,
      r2: 5
    },
    resource: 'r1',
    modifier: 2,
    priceJump: 1.1,
    owned: 0
  },
  g1: {
    name: "convert resource 1 to good 1",
    type: 'good',
    price: {
      r1: 5
    },
    resource: 'r1',
    resourceCost: 5,
    winRate: 0.5,
    good: 'g1',
    modifier: 1,
    priceJump: 1.1,
    owned: 0,
    total: 0,
    maxStorage: 500
  },
  g2: {
    name: "convert resource 2 to good 2",
    type: 'good',
    price: {
      r2: 5
    },
    resource: 'r2',
    resourceCost: 5,
    winRate: 0.5,
    good: 'g2',
    modifier: 1,
    priceJump: 1.1,
    owned: 0,
    total: 0,
    maxStorage: 500
  }
};<commit_msg>make items.js contain a python json.loads() decodeable string (except for the var = bit)<commit_after>var Gitems = {
  "r1": {
    "name": "resource 1",
    "type": "resource",
    "total": 0,
    "resource": "r1",
    "maxStorage": 10,
    "maxStoragePrice": 5,
    "maxStorageJump": 1.3,
    "maxStoragePriceJump": 1.2
  },
  "r2": {
    "name": "resource 2",
    "type": "resource",
    "total": 0,
    "resource": "r2",
    "maxStorage": 10,
    "maxStoragePrice": 5,
    "maxStorageJump": 2.0,
    "maxStoragePriceJump": 1.2
  },
  "i1": {
    "name": "1 more clicks on r1",
    "type": "item",
    "price": {
      "r1": 5
    },
    "resource": "r1",
    "modifier": 1,
    "priceJump": 1.1,
    "owned": 0
  },
  "i2": {
    "name": "1 more clicks on r2",
    "type": "item",
    "price": {
      "r2": 5
    },
    "resource": "r2",
    "modifier": 1,
    "priceJump": 1.1,
    "owned": 0
  },
  "i3": {
    "name": "1 more clicks on r2",
    "type": "item",
    "price": {
      "r1": 5,
      "r2": 5
    },
    "resource": "r1",
    "modifier": 2,
    "priceJump": 1.1,
    "owned": 0
  },
  "g1": {
    "name": "convert resource 1 to good 1",
    "type": "good",
    "price": {
      "r1": 5
    },
    "resource": "r1",
    "resourceCost": 5,
    "winRate": 0.5,
    "good": "g1",
    "modifier": 1,
    "priceJump": 1.1,
    "owned": 0,
    "total": 0,
    "maxStorage": 500
  },
  "g2": {
    "name": "convert resource 2 to good 2",
    "type": "good",
    "price": {
      "r2": 5
    },
    "resource": "r2",
    "resourceCost": 5,
    "winRate": 0.5,
    "good": "g2",
    "modifier": 1,
    "priceJump": 1.1,
    "owned": 0,
    "total": 0,
    "maxStorage": 500
  }
};<|endoftext|>


<commit_before>d3.json('index.json', function(err, index) {

    // Build initial title listing
    var titles = d3.select('#titles')
        .selectAll('li.title')
        .data(index.titles);

    var li = titles
        .enter()
        .append('li')
        .attr('class', 'title')
        .on('click', clickTitle)
        .append('div')
        .attr('class', 'clearfix');

    li.append('span')
        .attr('class', 'number')
        .text(function(d) { return d[0]; });

    li.append('span')
        .attr('class', 'name')
        .text(function(d) { return d[1]; });

    function clickTitle(d) {
        router.setRoute(d[0]);
        updateTitle(d[0]);
    }

    function findTitle(t) {
        var t = titles
            .classed('active', function(d) { return d[0] === t; })
            .filter(function(d,i) { return d[0] == t; });
        var d = t.data()[0];
        updateTitle(d[0]);
        sectionsFor(d);

        //Scroll to the item if we can't already see it
        var top = t.property('offsetTop');
        var tc = d3.select('.titles-container');
        if(top > tc.property('scrollTop') + tc.property('offsetHeight')-35){
            tc.property('scrollTop',top-35);
        }
    }

    function findSection(t, s) {
        updateTitle(s);
        //Only refresh section list if we're changing titles
        var currentTitle = d3.select(".title.active");
        if(currentTitle.empty() || currentTitle.data()[0][0] != t){
            findTitle(t);
        }

        var sections = d3.select('#sections')
            .selectAll('li.section')
            .classed('active',function(d) { return d[0] === s; });
        var section = sections
            .filter(function(d, i){ return d[0] === s; });

        //Scroll to the right part of the sections list if we can't see it
        var sectionsContainer = d3.select('.sections-container');
        if(section.property('offsetTop') > sectionsContainer.property('scrollTop') + sectionsContainer.property('offsetHeight')){
            sectionsContainer.property('scrollTop',section.property('offsetTop')-35);
        }

        doSection(section.data()[0]);
    }

    function doSection(d) {
        d3.select('#section').classed('loading', true);
        d3.json('sections/' + d[0] + '.json', function(err, section) {
            d3.select('#section').classed('loading', false);
            var s = d3.select('#section');

            var content = s.selectAll('div.content')
                .data([section], function(d) { return JSON.stringify(d); });

            content.exit().remove();

            var div = content.enter()
                .append('div')
                .attr('class', 'content');

            div.append('h1')
                .attr('class', 'pad1')
                .text(function(d) {
                    return d.heading.catch_text;
                });

            if (section.text) {
                div.append('div')
                    .attr('class', 'pad1')
                    .selectAll('p')
                    .data(function(d) {
                        return section.text.split(/\n+/);
                    })
                    .enter()
                    .append('p')
                    .html(function(d) {
                        return cited(d);
                    });
            }

            var sections = div.append('div')
                .attr('class', 'pad1')
                .selectAll('section')
                .data(section.sections, function(d) {
                    return d.prefix + d.text;
                });

            var sectionelem = sections.enter()
                .append('section')
                .attr('class', function(d) {
                    var c = '';
                    if (d.prefix.match(/([a-z])/)) c = 'section-1';
                    else if (d.prefix.match(/([0-9])/)) c = 'section-2';
                    else if (d.prefix.match(/([A-Z])/)) c = 'section-3';
                    return c;
                });
            sections.exit().remove();

            var section_p = sectionelem.append('p');

            section_p.append('span')
                .attr('class', 'section-prefix')
                .text(function(d) {
                    return d.prefix;
                });

            section_p.append('span')
                .html(function(d) {
                    return cited(d.text);
                });

            if (section.credits) {
                var credits = div.append('div')
                    .attr('class', 'pad1 limited-text');
                credits.append('h4')
                    .text('Credits');
                credits.append('p')
                    .html(function(d) {
                        return cited(d.credits);
                    });
            }

            if (section.historical) {
                var history = div.append('div')
                    .attr('class', 'pad1 limited-text');
                history.append('h4')
                    .text('Historical and Statutory');
                history.append('p')
                    .html(function(d) {
                        return cited(d.historical);
                    });
            }
        });
    }

    function doesNotApply(d) {
        return d[1].match(/\[(Repealed|Omitted|Expired)\]/g);
    }

    function cited(text) {
        return Citation.find(text, {
            context: {dc_code: {source: "dc_code"}},
            types: ["dc_code", "law", "stat"],
            replace: function(cite) {
                if (cite.type == "dc_code")
                    return "" + cite.match + "";
                else if (cite.type == "law")
                    return "" + cite.match + "";
            }
        }).text;
    }

    function urlFor(cite) {
        var url = "#/" + cite.dc_code.title + "/" + cite.dc_code.title + "-" + cite.dc_code.section;
        if (cite.dc_code.subsections.length > 0)
            url += "#" + cite.dc_code.subsections.join("/");
        return url;
    }

    function sectionsFor(title) {

        var data = index.sections.filter(function(s) {
            return s[0].match(/(\d+)\-/)[1] == title[0];
        });

        doSections(data);
    }

    function searchSection(s) {
        return index.sections.map(function(s) {
            return {
                title: s[0] + ' ' + s[1],
                value: s[0] + ' ' + s[1],
                type: 'section'
            };
        });
    }

    function doSections(data) {

        function clickSection(d) {
            router.setRoute(1,d[0]);
            updateTitle(d[0]);
        }

        // build section list
        var sections = d3.select('#sections')
            .selectAll('li.section')
            .data(data, function(d) {
                return d[0];
            });

        sections.exit().remove();

        var li = sections
            .enter()
            .append('li')
            .attr('class', 'section clearfix')
            .classed('repealed', doesNotApply)
            .on('click', clickSection);

        li.append('span')
            .attr('class', 'section-number')
            .text(function(d) { return d[0]; });

        li.append('span')
            .attr('class', 'section-name')
            .text(function(d) { return d[1]; });

    }

    function updateTitle(title) {
        if (title)
            d3.select('#code-identifier').text('§ ' + title);
        else d3.select('#code-identifier').text('');
    }

    var s = search();
    var combobox = d3.combobox();

    var title_search = d3.select('#search-title').on('keyup', function() {
            if (!this.value) return;
            if (this.value.match(/^(\d)\-/)) {
                return combobox.data(searchSection(this.value));
            }
            s.autocomplete(this.value, function(results) {
                combobox.data(results.map(function(r) {
                    return {
                        title: r,
                        value: r
                    };
                }));
            });
        })
        .call(combobox)
        .on('change', function() {
            var data = combobox.data();
            if (!data.length) return;
            if (data[0].type === 'section') {
                var path = this.value.split(' ')[0];
                var title = path.match(/^([\d]+)/)[0];
                router.setRoute(title + '/' + path);
                this.value = '';
            }
            s.query(this.value, function(d) {
                doSections(d.map(function(o) {
                    return o.title;
                }));
            });
        });

    var routes = {
        '#/:title': findTitle,
        '#/:title/:section': findSection
    };
    router = Router(routes);
    router.init();
});
<commit_msg>Do not re-query after finding sections<commit_after>d3.json('index.json', function(err, index) {

    // Build initial title listing
    var titles = d3.select('#titles')
        .selectAll('li.title')
        .data(index.titles);

    var li = titles
        .enter()
        .append('li')
        .attr('class', 'title')
        .on('click', clickTitle)
        .append('div')
        .attr('class', 'clearfix');

    li.append('span')
        .attr('class', 'number')
        .text(function(d) { return d[0]; });

    li.append('span')
        .attr('class', 'name')
        .text(function(d) { return d[1]; });

    function clickTitle(d) {
        router.setRoute(d[0]);
        updateTitle(d[0]);
    }

    function findTitle(t) {
        var t = titles
            .classed('active', function(d) { return d[0] === t; })
            .filter(function(d,i) { return d[0] == t; });
        var d = t.data()[0];
        updateTitle(d[0]);
        sectionsFor(d);

        // Scroll to the item if we can't already see it
        var top = t.property('offsetTop');
        var tc = d3.select('.titles-container');
        if(top > tc.property('scrollTop') + tc.property('offsetHeight')-35){
            tc.property('scrollTop',top-35);
        }
    }

    function findSection(t, s) {
        updateTitle(s);
        //Only refresh section list if we're changing titles
        var currentTitle = d3.select(".title.active");
        if (currentTitle.empty() || currentTitle.data()[0][0] != t) {
            findTitle(t);
        }

        var sections = d3.select('#sections')
            .selectAll('li.section')
            .classed('active',function(d) { return d[0] === s; });
        var section = sections
            .filter(function(d, i){ return d[0] === s; });

        //Scroll to the right part of the sections list if we can't see it
        var sectionsContainer = d3.select('.sections-container');
        if(section.property('offsetTop') > sectionsContainer.property('scrollTop') + sectionsContainer.property('offsetHeight')){
            sectionsContainer.property('scrollTop',section.property('offsetTop')-35);
        }

        doSection(section.data()[0]);
    }

    function doSection(d) {
        d3.select('#section').classed('loading', true);
        d3.json('sections/' + d[0] + '.json', function(err, section) {
            d3.select('#section').classed('loading', false);
            var s = d3.select('#section');

            var content = s.selectAll('div.content')
                .data([section], function(d) { return JSON.stringify(d); });

            content.exit().remove();

            var div = content.enter()
                .append('div')
                .attr('class', 'content');

            div.append('h1')
                .attr('class', 'pad1')
                .text(function(d) {
                    return d.heading.catch_text;
                });

            if (section.text) {
                div.append('div')
                    .attr('class', 'pad1')
                    .selectAll('p')
                    .data(function(d) {
                        return section.text.split(/\n+/);
                    })
                    .enter()
                    .append('p')
                    .html(function(d) {
                        return cited(d);
                    });
            }

            var sections = div.append('div')
                .attr('class', 'pad1')
                .selectAll('section')
                .data(section.sections, function(d) {
                    return d.prefix + d.text;
                });

            var sectionelem = sections.enter()
                .append('section')
                .attr('class', function(d) {
                    var c = '';
                    if (d.prefix.match(/([a-z])/)) c = 'section-1';
                    else if (d.prefix.match(/([0-9])/)) c = 'section-2';
                    else if (d.prefix.match(/([A-Z])/)) c = 'section-3';
                    return c;
                });
            sections.exit().remove();

            var section_p = sectionelem.append('p');

            section_p.append('span')
                .attr('class', 'section-prefix')
                .text(function(d) {
                    return d.prefix;
                });

            section_p.append('span')
                .html(function(d) {
                    return cited(d.text);
                });

            if (section.credits) {
                var credits = div.append('div')
                    .attr('class', 'pad1 limited-text');
                credits.append('h4')
                    .text('Credits');
                credits.append('p')
                    .html(function(d) {
                        return cited(d.credits);
                    });
            }

            if (section.historical) {
                var history = div.append('div')
                    .attr('class', 'pad1 limited-text');
                history.append('h4')
                    .text('Historical and Statutory');
                history.append('p')
                    .html(function(d) {
                        return cited(d.historical);
                    });
            }
        });
    }

    function doesNotApply(d) {
        return d[1].match(/\[(Repealed|Omitted|Expired)\]/g);
    }

    function cited(text) {
        return Citation.find(text, {
            context: {dc_code: {source: "dc_code"}},
            types: ["dc_code", "law", "stat"],
            replace: function(cite) {
                if (cite.type == "dc_code")
                    return "" + cite.match + "";
                else if (cite.type == "law")
                    return "" + cite.match + "";
            }
        }).text;
    }

    function urlFor(cite) {
        var url = "#/" + cite.dc_code.title + "/" + cite.dc_code.title + "-" + cite.dc_code.section;
        if (cite.dc_code.subsections.length > 0)
            url += "#" + cite.dc_code.subsections.join("/");
        return url;
    }

    function sectionsFor(title) {

        var data = index.sections.filter(function(s) {
            return s[0].match(/(\d+)\-/)[1] == title[0];
        });

        doSections(data);
    }

    function searchSection(s) {
        return index.sections.map(function(s) {
            return {
                title: s[0] + ' ' + s[1],
                value: s[0] + ' ' + s[1],
                type: 'section'
            };
        });
    }

    function doSections(data) {

        function clickSection(d) {
            router.setRoute(1,d[0]);
            updateTitle(d[0]);
        }

        // build section list
        var sections = d3.select('#sections')
            .selectAll('li.section')
            .data(data, function(d) {
                return d[0];
            });

        sections.exit().remove();

        var li = sections
            .enter()
            .append('li')
            .attr('class', 'section clearfix')
            .classed('repealed', doesNotApply)
            .on('click', clickSection);

        li.append('span')
            .attr('class', 'section-number')
            .text(function(d) { return d[0]; });

        li.append('span')
            .attr('class', 'section-name')
            .text(function(d) { return d[1]; });

    }

    function updateTitle(title) {
        if (title)
            d3.select('#code-identifier').text('§ ' + title);
        else d3.select('#code-identifier').text('');
    }

    var s = search();
    var combobox = d3.combobox();

    var title_search = d3.select('#search-title').on('keyup', function() {
            if (!this.value) return;
            if (this.value.match(/^(\d)\-/)) {
                return combobox.data(searchSection(this.value));
            }
            s.autocomplete(this.value, function(results) {
                combobox.data(results.map(function(r) {
                    return {
                        title: r,
                        value: r
                    };
                }));
            });
        })
        .call(combobox)
        .on('change', function() {
            var data = combobox.data();
            if (!data.length) return;
            if (data[0].type === 'section') {
                var path = this.value.split(' ')[0];
                var title = path.match(/^([\d]+)/)[0];
                router.setRoute(title + '/' + path);
                this.value = '';
                return;
            }
            s.query(this.value, function(d) {
                doSections(d.map(function(o) {
                    return o.title;
                }));
            });
        });

    var routes = {
        '#/:title': findTitle,
        '#/:title/:section': findSection
    };
    router = Router(routes);
    router.init();
});
<|endoftext|>


<commit_before>'use strict';

/*
	MUST FIND A MORE ELEGANT WAY
	check rootScope and as service

	Temporary solution for cleaner app code...

*/
/*var StandingsList    = [],
    ConstructorsList = [],
    DriversList      = [],
    CircuitsList     = [],
    nextEvent        = [],
    DriverStatus       = [];*/<commit_msg>removed temp file plain.js<commit_after><|endoftext|>


<commit_before>/******************************************************************************
 *                                  ~cassie~                                  *
 *                                ‾‾‾‾‾‾‾‾‾‾‾‾                                *
 * Simple future library for JS. Ready to be violently raped by Ajax!         *
 *     _________________________________________________________________      *
 *        Copyright (c) 2011 Quildreen Motta // Licenced under MIT/X11        *
 ******************************************************************************/

//// Module Cassie /////////////////////////////////////////////////////////////
//
// Cassie (short for lil' Cassandra) is a deliciously short future
// library for JavaScript. It provides a simple Promise object that can
// be used as the return value of any asynchronous call.
//
// The idea behind promises is that you can manipulate the return value
// of an asynchronous function before the function has been
// resolved. This allows you to write in a clear chaining DSL rather
// than using a clusterfuck of callbacks.
//
// Take an Ajax request, for example. The usual way:
//
//     var req = ajax.get('data.json'
//               , function (resp) { /* success */
//                   req.update('#data', JSON.parse(resp.responseText))
//               }
//               , function (resp) { /* fail */
//                   req.show_error()
//               })
//     setTimeout(req.fail, 10000)
//
// With promises, you could get this:
//
//     var req = ajax.get('data.json')
//                   .ok().update('#data')
//                   .failed().show_error()
//                   .timeout(10)
//
// You can pass this `req` object around, and other functions can add
// their own manipulations to the object as well:
//
//     function log_it(req) {
//         return req.ok(log('success'))
//                   .failed(log('failed'))
//                   .clear_timer() /* disables the timeout */
//     }
//     var req = log_it(ajax.get('data.json').timeout(10))
//

void function (root) { var cassie, old

    // Aliases for some long properties
    , slice = Array.prototype.slice

    // Constants with values for errors
    , forgotten = {}
    , timeouted = {}

    ///// Object Promise ///////////////////////////////////////////////////////
    //
    //    new ()
    //
    // Creates a deferred object, that can be manipulated before the
    // value is actually resolved. As soon as it is fulfilled, this
    // promise will apply the pending actions.
    //
    // You can either return the promise directly, or take full
    // advantage of it by inheriting the `Promise`'s prototoype and
    // adding your own custom methods.
    //
    function Promise () {
        this.callbacks     = {}
        this.flush_queue   = []
        this.value         = null
        this.timer         = null
        this.default_event = 'done'
    }
    Promise.prototype = function() {
        return { add:         add
               , flush:       flush
               , done:        done
               , bind:        bind
               , fail:        fail
               , timeout:     timeout
               , clear_timer: clear_timer
               , forget:      forget

               // Shortcuts for add(event[, callback])
               , ok:          ok
               , failed:      failed
               , timeouted:   _timeouted
               , forgotten:   _forgotten }

        ////// Function add_callback ///////////////////////////////////////////
        //
        //   (promise, event[, callback]) ⇒ Number
        //
        // Adds a callback to a `Promise`.
        //
        // :param: {Promise}  promise
        // :param: {String}   event
        // :param: {Function} callback
        //
        // The callback will be called whenever the given event happens
        // within the given promise, in the context of the promise (ie.:
        // `this` inside the callback will refer to the given promise)
        // and the arguments provided when the promise was fulfilled.
        //
        // :warning: side-effects
        //     The list of callbacks is modified in-place, if a truthy
        //     value is passed as the callback.
        //
        function add_callback(promise, event, callback) {
            return callback? get_queue(promise, event).push(callback)
                           : null
        }

        ////// Function get_queue //////////////////////////////////////////////
        //
        //   (promise, event) ⇒ Array
        //
        // Returns the list of callbacks for the given event.
        //
        // :param: {Promise} promise
        // :param: {String}  event
        //
        function get_queue(promise, event) {
            return  promise.callbacks[event]
                || (promise.callbacks[event] = [])
        }

        ////// Method add //////////////////////////////////////////////////////
        //
        //   (event[, callback]) ⇒ this
        //
        // Adds a callback to the `Promise`.
        //
        // :param: {String}   event
        // :param: {Function} callback
        //
        // Passing a callback directly to the add method is optional, to
        // leverage more specialized promises, Cassie supports defining
        // a default event to which callbacks are bound.
        //
        // For example, instead of writting the following:
        //
        //     promise.add('ok', foo).add('ok', bar)
        //
        // You could go with:
        //
        //     promise.add('ok').foo().bar()
        //
        // Of course, given you have a specialized promise implementing
        // `foo` and `bar`.
        //
        // The default event persists until another `add` call with an
        // explicit event is issued.
        //
        // :see also:
        //    * `add_callback` - for how the callbacks are added
        //
        function add(event, callback) {
            if (typeof event == 'function') {
                callback = event
                event    = this.default_event }

            this.default_event = event
            if (this.value)  fire(this, event, callback)
            else             add_callback(this, event, callback)

            return this
        }
        function fire(promise, event, callback) { var queue
            queue = get_queue(promise, event)
            if (callback && queue.flushed)
                return callback.apply(promise, promise.value)
        }

        ////// Method flush ////////////////////////////////////////////////////
        //
        //   (event) ⇒ this
        //
        // Calls all callbacks associated with the given event.
        //
        // :param: {String} event
        //
        // The callbacks are called in the context of this `Promise`
        // (ie.: `this` inside such callback will refer to this
        // `Promise`), and with any arguments that have been passed to
        // the promise when it was fulfilled.
        //
        function flush(event) {
            function next() { event = this.flush_queue.shift()      }
            function queue(){ event && this.flush_queue.push(event) }

            if (!this.value)  queue()
            else
                while (next())  flush_event(this, event)

            return this
        }

        function flush_event(promise, event) { var callbacks, current
            function next() { current = callbacks.shift() }

            callbacks = get_queue(this, event)
            while (next())  current.apply(promise, promise.value)

            callbacks.flushed = true
        }

        ////// Method done /////////////////////////////////////////////////////
        //
        //   (values) ⇒ this
        //
        // Resolves the promise to the given values and call the
        // callbacks defined for `done`.
        //
        // :param: {Array-like} values
        //
        // The given value array is cloned, then assigned to the
        // promise. This makes it easier for caller functions to just
        // pass the `arguments` object straight up to this function.
        //
        // If the promise has already been resolved, this method is a
        // noop.
        //
        function done(values) {
            if (!this.value) {
                this.clear_timer().flush('done')
                this.value = slice.call(values)
                this.flush() }
            return this
        }

        ////// Method fail /////////////////////////////////////////////////////
        //
        //   (error) ⇒ this
        //
        // Fails to fulfill the promise, and calls all the `failed`
        // callbacks passing the error as parameter.
        //
        // :param: {Object} error
        //
        // :see also:
        //    * `done`
        //
        function fail() {
            return this.flush('failed').done(arguments)
        }

        ////// Method bind /////////////////////////////////////////////////////
        //
        //    (values...) ⇒ this
        //
        // Successfully fulfills the promise, and calls the `bind`
        // callbacks passing the values as parameter.
        //
        // :see also:
        //    * `resolve`
        //
        function bind() {
            return this.flush('ok').done(arguments)
        }

        ////// Method timeout //////////////////////////////////////////////////
        //
        //   (delay) ⇒ this
        //
        // Fails to fulfill the promise after the given number of
        // seconds.
        //
        // :param: {Number} delay
        //
        // The promise fails with the value of `timeouted`.
        //
        function timeout(delay) { var self
            self = this
            this.timer = setTimeout(function(){
                self.flush('timeouted').fail(timeouted)
            }, delay * 1000)
            return this
        }

        ////// Method clear_timer //////////////////////////////////////////////
        //
        //   () ⇒ this
        //
        // Clears any timer that may exist for this promise.
        //
        function clear_timer() {
            clearTimeout(this.timer)
            return this
        }

        ////// Method forget ///////////////////////////////////////////////////
        //
        //   () ⇒ this
        //
        // Cancels the promise, and fails with the value of `forgotten`.
        //
        function forget() {
            return this.flush('forgotten').fail(forgotten)
        }

        ////// -Shortcuts for add(event[, callback]) ///////////////////////////
        function ok(fn)         { return this.add('ok',        fn) }
        function failed(fn)     { return this.add('failed',    fn) }
        function _timeouted(fn) { return this.add('timeouted', fn) }
        function _forgotten(fn) { return this.add('forgotten', fn) }
    }()

    ///// Exports //////////////////////////////////////////////////////////////
    if (typeof exports == 'undefined') {
        old    = root.cassie
        cassie = root.cassie = {}

        // Removes `cassie' from the global object
        cassie.make_local = function() {
            root.cassie = old
            return cassie }}
    else
        cassie = exports

    ////// -Properties under `cassie` //////////////////////////////////////////
    cassie.Promise   = Promise
    cassie.forgotten = forgotten
    cassie.timeouted = timeouted
}(this)<commit_msg>Fixes promise access in queue/next functions on flush.<commit_after>/******************************************************************************
 *                                  ~cassie~                                  *
 *                                ‾‾‾‾‾‾‾‾‾‾‾‾                                *
 * Simple future library for JS. Ready to be violently raped by Ajax!         *
 *     _________________________________________________________________      *
 *        Copyright (c) 2011 Quildreen Motta // Licenced under MIT/X11        *
 ******************************************************************************/

//// Module Cassie /////////////////////////////////////////////////////////////
//
// Cassie (short for lil' Cassandra) is a deliciously short future
// library for JavaScript. It provides a simple Promise object that can
// be used as the return value of any asynchronous call.
//
// The idea behind promises is that you can manipulate the return value
// of an asynchronous function before the function has been
// resolved. This allows you to write in a clear chaining DSL rather
// than using a clusterfuck of callbacks.
//
// Take an Ajax request, for example. The usual way:
//
//     var req = ajax.get('data.json'
//               , function (resp) { /* success */
//                   req.update('#data', JSON.parse(resp.responseText))
//               }
//               , function (resp) { /* fail */
//                   req.show_error()
//               })
//     setTimeout(req.fail, 10000)
//
// With promises, you could get this:
//
//     var req = ajax.get('data.json')
//                   .ok().update('#data')
//                   .failed().show_error()
//                   .timeout(10)
//
// You can pass this `req` object around, and other functions can add
// their own manipulations to the object as well:
//
//     function log_it(req) {
//         return req.ok(log('success'))
//                   .failed(log('failed'))
//                   .clear_timer() /* disables the timeout */
//     }
//     var req = log_it(ajax.get('data.json').timeout(10))
//

void function (root) { var cassie, old

    // Aliases for some long properties
    , slice = Array.prototype.slice

    // Constants with values for errors
    , forgotten = {}
    , timeouted = {}

    ///// Object Promise ///////////////////////////////////////////////////////
    //
    //    new ()
    //
    // Creates a deferred object, that can be manipulated before the
    // value is actually resolved. As soon as it is fulfilled, this
    // promise will apply the pending actions.
    //
    // You can either return the promise directly, or take full
    // advantage of it by inheriting the `Promise`'s prototoype and
    // adding your own custom methods.
    //
    function Promise () {
        this.callbacks     = {}
        this.flush_queue   = []
        this.value         = null
        this.timer         = null
        this.default_event = 'done'
    }
    Promise.prototype = function() {
        return { add:         add
               , flush:       flush
               , done:        done
               , bind:        bind
               , fail:        fail
               , timeout:     timeout
               , clear_timer: clear_timer
               , forget:      forget

               // Shortcuts for add(event[, callback])
               , ok:          ok
               , failed:      failed
               , timeouted:   _timeouted
               , forgotten:   _forgotten }

        ////// Function add_callback ///////////////////////////////////////////
        //
        //   (promise, event[, callback]) ⇒ Number
        //
        // Adds a callback to a `Promise`.
        //
        // :param: {Promise}  promise
        // :param: {String}   event
        // :param: {Function} callback
        //
        // The callback will be called whenever the given event happens
        // within the given promise, in the context of the promise (ie.:
        // `this` inside the callback will refer to the given promise)
        // and the arguments provided when the promise was fulfilled.
        //
        // :warning: side-effects
        //     The list of callbacks is modified in-place, if a truthy
        //     value is passed as the callback.
        //
        function add_callback(promise, event, callback) {
            return callback? get_queue(promise, event).push(callback)
                           : null
        }

        ////// Function get_queue //////////////////////////////////////////////
        //
        //   (promise, event) ⇒ Array
        //
        // Returns the list of callbacks for the given event.
        //
        // :param: {Promise} promise
        // :param: {String}  event
        //
        function get_queue(promise, event) {
            return  promise.callbacks[event]
                || (promise.callbacks[event] = [])
        }

        ////// Method add //////////////////////////////////////////////////////
        //
        //   (event[, callback]) ⇒ this
        //
        // Adds a callback to the `Promise`.
        //
        // :param: {String}   event
        // :param: {Function} callback
        //
        // Passing a callback directly to the add method is optional, to
        // leverage more specialized promises, Cassie supports defining
        // a default event to which callbacks are bound.
        //
        // For example, instead of writting the following:
        //
        //     promise.add('ok', foo).add('ok', bar)
        //
        // You could go with:
        //
        //     promise.add('ok').foo().bar()
        //
        // Of course, given you have a specialized promise implementing
        // `foo` and `bar`.
        //
        // The default event persists until another `add` call with an
        // explicit event is issued.
        //
        // :see also:
        //    * `add_callback` - for how the callbacks are added
        //
        function add(event, callback) {
            if (typeof event == 'function') {
                callback = event
                event    = this.default_event }

            this.default_event = event
            if (this.value)  fire(this, event, callback)
            else             add_callback(this, event, callback)

            return this
        }
        function fire(promise, event, callback) { var queue
            queue = get_queue(promise, event)
            if (callback && queue.flushed)
                return callback.apply(promise, promise.value)
        }

        ////// Method flush ////////////////////////////////////////////////////
        //
        //   (event) ⇒ this
        //
        // Calls all callbacks associated with the given event.
        //
        // :param: {String} event
        //
        // The callbacks are called in the context of this `Promise`
        // (ie.: `this` inside such callback will refer to this
        // `Promise`), and with any arguments that have been passed to
        // the promise when it was fulfilled.
        //
        function flush(event) { var self = this
            function next() { event = self.flush_queue.shift()      }
            function queue(){ event && self.flush_queue.push(event) }

            if (!this.value)  queue()
            else
                while (next())  flush_event(this, event)

            return this
        }

        function flush_event(promise, event) { var callbacks, current
            function next() { current = callbacks.shift() }

            callbacks = get_queue(this, event)
            while (next())  current.apply(promise, promise.value)

            callbacks.flushed = true
        }

        ////// Method done /////////////////////////////////////////////////////
        //
        //   (values) ⇒ this
        //
        // Resolves the promise to the given values and call the
        // callbacks defined for `done`.
        //
        // :param: {Array-like} values
        //
        // The given value array is cloned, then assigned to the
        // promise. This makes it easier for caller functions to just
        // pass the `arguments` object straight up to this function.
        //
        // If the promise has already been resolved, this method is a
        // noop.
        //
        function done(values) {
            if (!this.value) {
                this.clear_timer().flush('done')
                this.value = slice.call(values)
                this.flush() }
            return this
        }

        ////// Method fail /////////////////////////////////////////////////////
        //
        //   (error) ⇒ this
        //
        // Fails to fulfill the promise, and calls all the `failed`
        // callbacks passing the error as parameter.
        //
        // :param: {Object} error
        //
        // :see also:
        //    * `done`
        //
        function fail() {
            return this.flush('failed').done(arguments)
        }

        ////// Method bind /////////////////////////////////////////////////////
        //
        //    (values...) ⇒ this
        //
        // Successfully fulfills the promise, and calls the `bind`
        // callbacks passing the values as parameter.
        //
        // :see also:
        //    * `resolve`
        //
        function bind() {
            return this.flush('ok').done(arguments)
        }

        ////// Method timeout //////////////////////////////////////////////////
        //
        //   (delay) ⇒ this
        //
        // Fails to fulfill the promise after the given number of
        // seconds.
        //
        // :param: {Number} delay
        //
        // The promise fails with the value of `timeouted`.
        //
        function timeout(delay) { var self
            self = this
            this.timer = setTimeout(function(){
                self.flush('timeouted').fail(timeouted)
            }, delay * 1000)
            return this
        }

        ////// Method clear_timer //////////////////////////////////////////////
        //
        //   () ⇒ this
        //
        // Clears any timer that may exist for this promise.
        //
        function clear_timer() {
            clearTimeout(this.timer)
            return this
        }

        ////// Method forget ///////////////////////////////////////////////////
        //
        //   () ⇒ this
        //
        // Cancels the promise, and fails with the value of `forgotten`.
        //
        function forget() {
            return this.flush('forgotten').fail(forgotten)
        }

        ////// -Shortcuts for add(event[, callback]) ///////////////////////////
        function ok(fn)         { return this.add('ok',        fn) }
        function failed(fn)     { return this.add('failed',    fn) }
        function _timeouted(fn) { return this.add('timeouted', fn) }
        function _forgotten(fn) { return this.add('forgotten', fn) }
    }()

    ///// Exports //////////////////////////////////////////////////////////////
    if (typeof exports == 'undefined') {
        old    = root.cassie
        cassie = root.cassie = {}

        // Removes `cassie' from the global object
        cassie.make_local = function() {
            root.cassie = old
            return cassie }}
    else
        cassie = exports

    ////// -Properties under `cassie` //////////////////////////////////////////
    cassie.Promise   = Promise
    cassie.forgotten = forgotten
    cassie.timeouted = timeouted
}(this)<|endoftext|>