<commit_before>/*
    DESCRIPTION:
        Perform YouTube searches using Youtube's Data API

    AUTHOR:
        Cristian Baldi

    CONFIGURATION:
        Enable YouTube Data API v3
        GOOGLE_API_KEY - https://code.google.com/apis/console/

    COMMANDS:
        !yt <query>

    EXAMPLE:
        You: !yt test
        Bot: title - link
*/

var request = require('request');
var Util = require('./../src/Util');

var youtube = function(){

    var GOOGLE_API_KEY = process.env.GOOGLE_API_KEY || ""

    this.help = {
        shortDescription: "Search for videos on YouTube.",
        fullHelp: "Use `/youtube query` or `/yt query` to seach for `query` on YouTube."
    };

    this.check = function(){
        return GOOGLE_API_KEY == "" ? false : true;
    };

    this.on("text", function (msg, reply){

        var match = Util.parseCommand(msg.text,["yt","youtube", "video"], {joinParams: true});

        if(match){

            reply({type:"status", status: "typing"});

            query = match[1];

            if(query.length > 0){
                console.log("\tYT: " + query);
                link = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=" + encodeURIComponent(query) + "&key=" + GOOGLE_API_KEY;

                request(link, function (error, response, data) {
                    if (!error && response.statusCode == 200) {

                        data = JSON.parse(data);

                        if(data["items"])
                        {
                            var videos = data["items"];

                            for (i=0,len=videos.length;i<len;i++)
                            {
                                if(videos[i]["id"]["kind"] == "youtube#video")
                                {
                                    videoId = videos[i]["id"]["videoId"];
                                    videoTitle = videos[i]["snippet"]["title"];
                                    reply({type:"text", text: "[" + videoTitle + "]" + "(" + "http://www.youtube.com/watch?v=" + videoId + ")", options:{parse_mode: "Markdown"}})
                                }
                            }
                        }
                    }
                });

            }
        }
    });

};

module.exports = youtube;<commit_msg>Fixed youtube again<commit_after>/*
    DESCRIPTION:
        Perform YouTube searches using Youtube's Data API

    AUTHOR:
        Cristian Baldi

    CONFIGURATION:
        Enable YouTube Data API v3
        GOOGLE_API_KEY - https://code.google.com/apis/console/

    COMMANDS:
        !yt <query>

    EXAMPLE:
        You: !yt test
        Bot: title - link
*/

var request = require('request');
var Util = require('./../src/Util');

var youtube = function(){

    var GOOGLE_API_KEY = process.env.GOOGLE_API_KEY || ""

    this.help = {
        shortDescription: "Search for videos on YouTube.",
        fullHelp: "Use `/youtube query` or `/yt query` to seach for `query` on YouTube."
    };

    this.check = function(){
        return GOOGLE_API_KEY == "" ? false : true;
    };

    this.on("text", function (msg, reply){

        var match = Util.parseCommand(msg.text,["yt","youtube", "video"], {joinParams: true});

        if(match){

            reply({type:"status", status: "typing"});

            query = match[1];

            if(query.length > 0){
                console.log("\tYT: " + query);
                link = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=" + encodeURIComponent(query) + "&key=" + GOOGLE_API_KEY;

                request(link, function (error, response, data) {
                    if (!error && response.statusCode == 200) {

                        data = JSON.parse(data);

                        if(data["items"])
                        {
                            var videos = data["items"];

                            for (i=0,len=videos.length;i<len;i++)
                            {
                                if(videos[i]["id"]["kind"] == "youtube#video")
                                {
                                    videoId = videos[i]["id"]["videoId"];
                                    videoTitle = videos[i]["snippet"]["title"];
                                    reply({type:"text", text: "[" + videoTitle + "]" + "(" + "http://www.youtube.com/watch?v=" + videoId + ")", options:{parse_mode: "Markdown"}})
                                    break;
                                }
                            }
                        }
                    }
                });

            }
        }
    });

};

module.exports = youtube;<|endoftext|>


<commit_before>// script from https://joonasjokinen.fi/blog/how-to-generate-rss-feed-nextjs

const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');

// const matter = require('frontmatter-markdown-loader');

const getAllPosts = () => {
  // Read the posts directory and get blog posts' filenames
  const filenames = fs.readdirSync(path.join('data/posts-tw'));
  // const enFilenames = fs.readdirSync(path.join('data/posts-en'));

  // Go through all filenames
  const posts = filenames.map((filename) => {
    // Full path to the file from project root
    const filepath = path.join('data/posts-tw/', filename);

    // Read the content of the file
    const markdownWithMeta = fs.readFileSync(filepath, 'utf-8');

    // Get all data from the frontmatter
    const { data } = matter(markdownWithMeta);
    // Return everything we need for the RSS feed
    return {
      title: data.title,
      date: data.date,
      excerpt: data.excerpt,
      link: 'https://blog.wildsky.cc/posts' + filename.replace('.md', ''),
    };
  });
  // Sort the blogposts by date
  return posts.sort((a, b) => new Date(b.date) - new Date(a.date));
};

const getXmlItems = (blogPosts) => {
  return blogPosts
    .map((post) => {
      return `<item>
            <title>${post.title}</title>
            <link>${post.link}</link>
            <guid>${post.link}</guid>
            <pubDate>${new Date(post.date).toUTCString()}</pubDate>
            <description>${post.excerpt}</description>
            <dc:creator>Geng-Zh W. Fann</dc:creator>
        </item>
        `;
    })
    .join(''); // Join the array of items into a single long string
};

const getRssXml = (xmlItems, latestPostDate) => {
  return `<?xml version="1.0" ?>
  <rss
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    version="2.0"
  >
    <channel>
        <title>Wildsky F.</title>
        <link>https://blog.wildsky.cc</link>
        <description>Easy things should be easy, and hard things should be possible.</description>
        <language>zh-tw</language>
        <lastBuildDate>${new Date(latestPostDate).toUTCString()}</lastBuildDate>
        ${xmlItems}
    </channel>
  </rss>`;
};

(() => {
  const postData = getAllPosts();
  const xmlItems = getXmlItems(postData);
  const rssXml = getRssXml(xmlItems, postData[0].date);

  fs.writeFile(path.join('public', 'rss.xml'), rssXml, (err) => {
    if (err) {
      console.log(err);
    } else {
      console.log('RSS feed written successfully');
    }
  });
})();
<commit_msg>update rss generator<commit_after>// script from https://joonasjokinen.fi/blog/how-to-generate-rss-feed-nextjs

const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');

// const matter = require('frontmatter-markdown-loader');

const getAllPosts = () => {
  const filenames = fs.readdirSync(path.join('data/posts-tw'));
  // const enFilenames = fs.readdirSync(path.join('data/posts-en'));

  const posts = filenames.map((filename) => {
    const filepath = path.join('data/posts-tw/', filename);
    const markdownWithMeta = fs.readFileSync(filepath, 'utf-8');
    const { data } = matter(markdownWithMeta);

    return {
      title: data.title,
      date: data.date,
      excerpt: `<img
        src="https://blog.wildsky.cc/images/${data.feature_image}"
        alt="${data.title}'s feature image"
      /> ${data.excerpt}`,
      link: 'https://blog.wildsky.cc/posts' + filename.replace('.md', ''),
    };
  });
  // Sort the blogposts by date
  return posts.sort((a, b) => new Date(b.date) - new Date(a.date));
};

const CDATA = (content) => `<![CDATA[${content}]]>`

const getRssXml = (blogPosts, latestPostDate) => `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
>
  <channel>
      <title>Wildsky F.</title>
      <atom:link href="https://blog.wildsky.cc/rss.xml" rel="self" type="application/rss+xml" />
      <link>https://blog.wildsky.cc</link>
      <description>Easy things should be easy, and hard things should be possible.</description>
      <language>zh-tw</language>
      <sy:updatePeriod>hourly</sy:updatePeriod>
      <sy:updateFrequency>1</sy:updateFrequency>
      <lastBuildDate>${new Date(latestPostDate).toUTCString()}</lastBuildDate>
      ${blogPosts.map((post) => `
        <item>
            <title>${CDATA(post.title)}</title>
            <link>${post.link}</link>
            <guid>${post.link}</guid>
            <pubDate>${new Date(post.date).toUTCString()}</pubDate>
            <description>${CDATA(post.excerpt)}</description>
            <dc:creator>${CDATA('Geng-Zh W. Fann')}</dc:creator>
        </item>`
      ).join('')}
  </channel>
</rss>`;

(() => {
  const postData = getAllPosts();
  const rssXml = getRssXml(postData, postData[0].date);

  fs.writeFile(path.join('public', 'rss.xml'), rssXml, (err) => {
    if (err) {
      console.log(err);
    } else {
      console.log('RSS feed written successfully');
    }
  });
})();
<|endoftext|>


<commit_before>$("#homepageTemplate").on('complete.cycle',function(state,$ele,infoObj){
	$('.productSlideshow',$ele).cycle();
	});

//unbind this from window anytime a category page is left.
//NOTE! if infinite prodlist is used on other pages, remove run this on that template as well.
$("#categoryTemplate").on('complete.infinitescroll',function(state,$ele,infoObj){
	$(window).off('scroll.infiniteScroll');
	});

$("#productTemplate, #productTemplateQuickView").on('complete.dynimaging',function(state,$ele,infoObj){
	handleSrcSetUpdate($ele);
	$('.prodDetailImagesContainer',$ele).imagegallery({
		show: 'fade',
		hide: 'fade',
		fullscreen: false,
		slideshow: false
		});
	});

//stops the video from playing when a user leaves the detail page (or quickview)
$("#productTemplate, #productTemplateQuickView").on('depart.youtubeReset',function(state,$ele,infoObj){
	var $iframe = $("[data-app-role='videoContainer']:first",$ele).find('iframe:first');
	var video = $iframe.attr('src');
	$iframe.attr('src',''); //this stops the video.
	$iframe.attr('src',$iframe.src); //set the src so if the page is visited again, the video is present.
	});

myApp.rq.push(['script',0,(document.location.protocol == 'file:') ? myApp.vars.testURL+'jsonapi/config.js' : myApp.vars.baseURL+'jsonapi/config.js',function(){
//in some cases, such as the zoovy UI, zglobals may not be defined. If that's the case, certain vars, such as jqurl, must be passed in via P in initialize:
//	myApp.u.dump(" ->>>>>>>>>>>>>>>>>>>>>>>>>>>>> zGlobals is an object");
	window.myApp.vars.username = zGlobals.appSettings.username.toLowerCase(); //used w/ image URL's.
//need to make sure the secureURL ends in a / always. doesn't seem to always come in that way via zGlobals
	window.myApp.vars.secureURL = zGlobals.appSettings.https_app_url;
	window.myApp.vars.domain = zGlobals.appSettings.sdomain; //passed in ajax requests.
	window.myApp.vars.jqurl = (document.location.protocol === 'file:') ? myApp.vars.testURL+'jsonapi/' : '/jsonapi/';
	}]); //The config.js is dynamically generated.

myApp.rq.push(['extension',0,'order_create','extensions/checkout/extension.js']);
myApp.rq.push(['extension',0,'cco','extensions/cart_checkout_order.js']);
myApp.rq.push(['extension',0,'greenspeed','app-greenspeed.js']);

myApp.rq.push(['extension',0,'store_prodlist','extensions/store_prodlist.js']);
myApp.rq.push(['extension',0,'store_navcats','extensions/store_navcats.js']);
myApp.rq.push(['extension',0,'prodlist_infinite','extensions/prodlist_infinite.js']);
myApp.rq.push(['extension',0,'store_search','extensions/store_search.js']);
myApp.rq.push(['extension',0,'store_product','extensions/store_product.js']);
myApp.rq.push(['extension',0,'store_crm','extensions/store_crm.js']);
myApp.rq.push(['extension',0,'quickstart','app-quickstart.js','startMyProgram']);
myApp.rq.push(['extension',0,'cart_message','extensions/cart_message/extension.js']);
myApp.rq.push(['extension',0,'store_routing','extensions/store_routing.js']);

myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jsonpath.0.8.0.js']); //used pretty early in process..

//once peg is loaded, need to retrieve the grammar file. Order is important there. This will validate the file too.
myApp.u.loadScript(myApp.vars.baseURL+'resources/peg-0.8.0.js',function(){
	myApp.model.getGrammar(myApp.vars.baseURL+"resources/pegjs-grammar-20140203.pegjs");
	}); // ### TODO -> callback on RQ.push wasn't getting executed. investigate.

myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/tlc.js']); //in zero pass in case product page is first page.
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jquery.showloading-v1.0.jt.js']); //used pretty early in process..
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jquery.ui.anyplugins.js']); //in zero pass in case product page is first page.
myApp.rq.push(['css',1,myApp.vars.baseURL+'resources/anyplugins.css']);

myApp.rq.push(['script',0,myApp.vars.baseURL+'jquery.cycle2.min.js']); //used pretty early in process..
myApp.rq.push(['script',0,myApp.vars.baseURL+'jquery.cycle2.swipe.min.js']); //in zero pass in case product page is first page

//used for image enlargement in product layout
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/load-image.min.js']); //in zero pass in case product page is first page.
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jquery.image-gallery.jt.js']); //in zero pass in case product page is first page.

//myApp.rq.push(['script',0,myApp.vars.baseURL+'srcset-polyfill-1.1.1-jt.js']); //in zero pass in case product page is first page.

//gets executed from app-admin.html as part of controller init process.
//progress is an object that will get updated as the resources load.
/*
'passZeroResourcesLength' : [INT],
'passZeroResourcesLoaded' : [INT],
'passZeroTimeout' : null //the timeout instance running within loadResources that updates this object. it will run indef unless clearTimeout run here OR all resources are loaded.

*/
myApp.u.showProgress = function(progress)	{
	function showProgress(attempt)	{
		if(progress.passZeroResourcesLength == progress.passZeroResourcesLoaded)	{
			//All pass zero resources have loaded.
			//the app will handle hiding the loading screen.
			myApp.u.appInitComplete();
			}
		else if(attempt > 150)	{
			//hhhhmmm.... something must have gone wrong.
			clearTimeout(progress.passZeroTimeout); //end the resource loading timeout.
			$('.appMessaging','#appPreView').anymessage({'message':'Init failed to load all the resources within a reasonable number of attempts.','gMessage':true,'persistent':true});
			}
		else	{
			var percentPerInclude = (100 / progress.passZeroResourcesLength);
			var percentComplete = Math.round(progress.passZeroResourcesLength * percentPerInclude); //used to sum how many includes have successfully loaded.
//			dump(" -> percentPerInclude: "+percentPerInclude+" and percentComplete: "+percentComplete);
			$('#appPreViewProgressBar').val(percentComplete);
			$('#appPreViewProgressText').empty().append(percentComplete+"% Complete");
			attempt++;
			setTimeout(function(){showProgress(attempt);},250);
			}
		}
	showProgress(0)
	}

//Any code that needs to be executed after the app init has occured can go here.
//will pass in the page info object. (pageType, templateID, pid/navcat/show and more)
myApp.u.appInitComplete = function(P)	{
	myApp.u.dump("Executing myAppIsLoaded code...");

	dump(" -> HEY! just a head's up, the default pageTransition was just overwritten from app-greenspeed-init.js");
	myApp.ext.quickstart.pageTransition = function($o,$n,infoObj){
		//showing the before before anything shows up in the dropdown when it is clicked is not user friendly.
		if(myApp.ext.quickstart.vars.hotw.length > 1)	{
			$('#hotwButton').show();
			}
		function transitionThePage()	{
//if $o doesn't exist, the animation doesn't run and the new element doesn't show up, so that needs to be accounted for.
//$o MAY be a jquery instance but have no length, so check both.
			if($o instanceof jQuery && $o.length)	{
				$('#mainContentArea').height($o.outerHeight()); //add a fixed height temporarily so that page doesn't 'collapse'
				dump(" -> offsetleft: " + $('#mainContentArea').width());
				$o.animate({'height':20,'width':20,'overflow':'hidden','left':$('#mainContentArea').width(),'top':-30},function(){
					$('#mainContentArea').height('');
					$o.removeAttr('style').hide();
					$n.css({'position':'relative','z-index':10}); //
					});
				}
			else	{
				//if o isn't set, n needs to be reset to relative positioning or the display will be wonky.
				$n.css({'position':'relative','z-index':10}); //
				}

			}

		$o.css({'position':'relative','z-index':'10'}); //make sure the old page is 'above' the new.
//$n isn't populated yet, most likely. So instead of animating it, just show it. Then animate the old layer above it.
		if($n instanceof jQuery && $o instanceof jQuery && ($n.attr('id') == $o.attr('id')))	{} //old and new match. do nothing.
		if($n instanceof jQuery)	{
			$n.addClass('pageTemplateBG').css({position:'absolute','z-index':9,'left':0,'top':0,'right':0}).show();
			}
//only 'jump to top' if we are partially down the page.  Using the header height as > means if a link in the header is clicked, we don't jump down to the content area. feels natural that way.
		if(infoObj.performJumpToTop && $(window).scrollTop() > $('header','#appView').height())	{
			$('html, body').animate({scrollTop : ($('header','#appView').length ? $('header','#appView').first().height() : 0)},500,function(){
				transitionThePage();
				});
			} //new page content loading. scroll to top.
		else	{
			transitionThePage();
			}

		}

	//Cart Messaging Responses.
	myApp.cmr.push(['chat.join',function(message){
		if(message.FROM == 'ADMIN')	{
			var $ui = myApp.ext.quickstart.a.showBuyerCMUI();
			$("[data-app-role='messageInput']",$ui).show();
			$("[data-app-role='messageHistory']",$ui).append("
"+message.FROM+" has joined the chat.<\/p>");
			$('.show4ActiveChat',$ui).show();
			$('.hide4ActiveChat',$ui).hide();
			}
		}]);

	//the default behavior for an itemAppend is to show the chat portion of the dialog. that's an undesired behavior from the buyer perspective (chat only works if admin is actively listening).
	myApp.cmr.push(['cart.itemAppend',function(message,$context)	{
		$("[data-app-role='messageHistory']",$context).append("
"+message.FROM+" has added item "+message.sku+" to the cart.<\/p>");
		}]);

	myApp.cmr.push(['goto',function(message,$context){
		var $history = $("[data-app-role='messageHistory']",$context);
		$P = $("
")
			.addClass('chat_post')
			.append(""+message.FROM+"<\/span> has sent over a "+(message.vars.pageType || "")+" link for you within this store. Click here<\/span> to view.")
			.on('click',function(){
				showContent(myApp.ext.quickstart.u.whatAmIFor(message.vars),message.vars);
				});
		$history.append($P);
		$history.parent().scrollTop($history.height());
		}]);

	myApp.ext.order_create.checkoutCompletes.push(function(vars,$checkout){
//append this to
		$("[data-app-role='thirdPartyContainer']",$checkout).append("
What next?

");
		$('.ocmTwitterComment',$checkout).click(function(){
			window.open('http://twitter.com/home?status='+cartContentsAsLinks,'twitter');
			_gaq.push(['_trackEvent','Checkout','User Event','Tweeted about order']);
			});
		//the fb code only works if an appID is set, so don't show banner if not present.
		if(myApp.u.thisNestedExists("zGlobals.thirdParty.facebook.appId") && typeof FB == 'object')	{
			$('.ocmFacebookComment',$checkout).click(function(){
				myApp.ext.quickstart.thirdParty.fb.postToWall(cartContentsAsLinks);
				_gaq.push(['_trackEvent','Checkout','User Event','FB message about order']);
				});
			}
		else	{$('.ocmFacebookComment').hide()}
		});
	}

//this will trigger the content to load on app init. so if you push refresh, you don't get a blank page.
//it'll also handle the old 'meta' uri params.
//this will trigger the content to load on app init. so if you push refresh, you don't get a blank page.
//it'll also handle the old 'meta' uri params.
myApp.router.appendInit({
	'type':'function',
	'route': function(v){
		return {'init':true} //returning anything but false triggers a match.
		},
	'callback':function(f,g){
		dump(" -> triggered callback for appendInit");
		g = g || {};
		if(g.uriParams.seoRequest){
			showContent(g.uriParams.pageType, g.uriParams);
			}
		else if(document.location.hash)	{
			myApp.u.dump('triggering handleHash');
			myApp.router.handleHashChange();
			}
		else	{
			//IE8 didn't like the shortcut to showContent here.
			myApp.ext.quickstart.a.showContent('homepage');
			}
		if(g.uriParams && g.uriParams.meta)	{
			myApp.ext.cco.calls.cartSet.init({'want/refer':infoObj.uriParams.meta,'cartID':_app.model.fetchCartID()},{},'passive');
			}
		if(g.uriParams && g.uriParams.meta_src)	{
			myApp.ext.cco.calls.cartSet.init({'want/refer_src':infoObj.uriParams.meta_src,'cartID':_app.model.fetchCartID()},{},'passive');
			}
		}
	});
<commit_msg>updates to init<commit_after>$("#homepageTemplate").on('complete.cycle',function(state,$ele,infoObj){
	$('.productSlideshow',$ele).cycle();
	});

//unbind this from window anytime a category page is left.
//NOTE! if infinite prodlist is used on other pages, remove run this on that template as well.
$("#categoryTemplate").on('complete.infinitescroll',function(state,$ele,infoObj){
	$(window).off('scroll.infiniteScroll');
	});

$("#productTemplate, #productTemplateQuickView").on('complete.dynimaging',function(state,$ele,infoObj){
	handleSrcSetUpdate($ele);
	$('.prodDetailImagesContainer',$ele).imagegallery({
		show: 'fade',
		hide: 'fade',
		fullscreen: false,
		slideshow: false
		});
	});

//stops the video from playing when a user leaves the detail page (or quickview)
$("#productTemplate, #productTemplateQuickView").on('depart.youtubeReset',function(state,$ele,infoObj){
	var $iframe = $("[data-app-role='videoContainer']:first",$ele).find('iframe:first');
	var video = $iframe.attr('src');
	$iframe.attr('src',''); //this stops the video.
	$iframe.attr('src',$iframe.src); //set the src so if the page is visited again, the video is present.
	});

myApp.rq.push(['script',0,(document.location.protocol == 'file:') ? myApp.vars.testURL+'jsonapi/config.js' : myApp.vars.baseURL+'jsonapi/config.js',function(){
//in some cases, such as the zoovy UI, zglobals may not be defined. If that's the case, certain vars, such as jqurl, must be passed in via P in initialize:
//	myApp.u.dump(" ->>>>>>>>>>>>>>>>>>>>>>>>>>>>> zGlobals is an object");
	window.myApp.vars.username = zGlobals.appSettings.username.toLowerCase(); //used w/ image URL's.
//need to make sure the secureURL ends in a / always. doesn't seem to always come in that way via zGlobals
	window.myApp.vars.secureURL = zGlobals.appSettings.https_app_url;
	window.myApp.vars.domain = zGlobals.appSettings.sdomain; //passed in ajax requests.
	window.myApp.vars.jqurl = (document.location.protocol === 'file:') ? myApp.vars.testURL+'jsonapi/' : '/jsonapi/';
	}]); //The config.js is dynamically generated.

myApp.rq.push(['extension',0,'order_create','extensions/checkout/extension.js']);
myApp.rq.push(['extension',0,'cco','extensions/cart_checkout_order.js']);
myApp.rq.push(['extension',0,'greenspeed','app-greenspeed.js']);

myApp.rq.push(['extension',0,'store_prodlist','extensions/store_prodlist.js']);
myApp.rq.push(['extension',0,'store_navcats','extensions/store_navcats.js']);
myApp.rq.push(['extension',0,'prodlist_infinite','extensions/prodlist_infinite.js']);
myApp.rq.push(['extension',0,'store_search','extensions/store_search.js']);
myApp.rq.push(['extension',0,'store_product','extensions/store_product.js']);
myApp.rq.push(['extension',0,'store_crm','extensions/store_crm.js']);
myApp.rq.push(['extension',0,'quickstart','app-quickstart.js','startMyProgram']);
myApp.rq.push(['extension',0,'cart_message','extensions/cart_message/extension.js']);
myApp.rq.push(['extension',0,'store_routing','extensions/store_routing.js']);

myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jsonpath.0.8.0.js']); //used pretty early in process..

//once peg is loaded, need to retrieve the grammar file. Order is important there. This will validate the file too.
myApp.u.loadScript(myApp.vars.baseURL+'resources/peg-0.8.0.js',function(){
	myApp.model.getGrammar(myApp.vars.baseURL+"resources/pegjs-grammar-20140203.pegjs");
	}); // ### TODO -> callback on RQ.push wasn't getting executed. investigate.

myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/tlc.js']); //in zero pass in case product page is first page.
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jquery.showloading-v1.0.jt.js']); //used pretty early in process..
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jquery.ui.anyplugins.js']); //in zero pass in case product page is first page.
myApp.rq.push(['css',1,myApp.vars.baseURL+'resources/anyplugins.css']);

myApp.rq.push(['script',0,myApp.vars.baseURL+'jquery.cycle2.min.js']); //used pretty early in process..
myApp.rq.push(['script',0,myApp.vars.baseURL+'jquery.cycle2.swipe.min.js']); //in zero pass in case product page is first page

//used for image enlargement in product layout
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/load-image.min.js']); //in zero pass in case product page is first page.
myApp.rq.push(['script',0,myApp.vars.baseURL+'resources/jquery.image-gallery.jt.js']); //in zero pass in case product page is first page.

//myApp.rq.push(['script',0,myApp.vars.baseURL+'srcset-polyfill-1.1.1-jt.js']); //in zero pass in case product page is first page.

//gets executed from app-admin.html as part of controller init process.
//progress is an object that will get updated as the resources load.
/*
'passZeroResourcesLength' : [INT],
'passZeroResourcesLoaded' : [INT],
'passZeroTimeout' : null //the timeout instance running within loadResources that updates this object. it will run indef unless clearTimeout run here OR all resources are loaded.

*/
myApp.u.showProgress = function(progress)	{
	function showProgress(attempt)	{
		if(progress.passZeroResourcesLength == progress.passZeroResourcesLoaded)	{
			//All pass zero resources have loaded.
			//the app will handle hiding the loading screen.
			myApp.u.appInitComplete();
			}
		else if(attempt > 150)	{
			//hhhhmmm.... something must have gone wrong.
			clearTimeout(progress.passZeroTimeout); //end the resource loading timeout.
			$('.appMessaging','#appPreView').anymessage({'message':'Init failed to load all the resources within a reasonable number of attempts.','gMessage':true,'persistent':true});
			}
		else	{
			var percentPerInclude = (100 / progress.passZeroResourcesLength);
			var percentComplete = Math.round(progress.passZeroResourcesLength * percentPerInclude); //used to sum how many includes have successfully loaded.
//			dump(" -> percentPerInclude: "+percentPerInclude+" and percentComplete: "+percentComplete);
			$('#appPreViewProgressBar').val(percentComplete);
			$('#appPreViewProgressText').empty().append(percentComplete+"% Complete");
			attempt++;
			setTimeout(function(){showProgress(attempt);},250);
			}
		}
	showProgress(0)
	}

//Any code that needs to be executed after the app init has occured can go here.
//will pass in the page info object. (pageType, templateID, pid/navcat/show and more)
myApp.u.appInitComplete = function(P)	{
	myApp.u.dump("Executing myAppIsLoaded code...");

	dump(" -> HEY! just a head's up, the default pageTransition was just overwritten from app-greenspeed-init.js");
	myApp.ext.quickstart.pageTransition = function($o,$n,infoObj){
		//showing the before before anything shows up in the dropdown when it is clicked is not user friendly.
		if(myApp.ext.quickstart.vars.hotw.length > 1)	{
			$('#hotwButton').show();
			}
		function transitionThePage()	{
//if $o doesn't exist, the animation doesn't run and the new element doesn't show up, so that needs to be accounted for.
//$o MAY be a jquery instance but have no length, so check both.
			if($o instanceof jQuery && $o.length)	{
				$('#mainContentArea').height($o.outerHeight()); //add a fixed height temporarily so that page doesn't 'collapse'
				dump(" -> offsetleft: " + $('#mainContentArea').width());
				$o.animate({'height':20,'width':20,'overflow':'hidden','left':$('#mainContentArea').width(),'top':-30},function(){
					$('#mainContentArea').height('');
					$o.removeAttr('style').hide();
					$n.css({'position':'relative','z-index':10}); //
					});
				}
			else	{
				//if o isn't set, n needs to be reset to relative positioning or the display will be wonky.
				$n.css({'position':'relative','z-index':10}); //
				}

			}

		$o.css({'position':'relative','z-index':'10'}); //make sure the old page is 'above' the new.
//$n isn't populated yet, most likely. So instead of animating it, just show it. Then animate the old layer above it.
		if($n instanceof jQuery && $o instanceof jQuery && ($n.attr('id') == $o.attr('id')))	{} //old and new match. do nothing.
		if($n instanceof jQuery)	{
			$n.addClass('pageTemplateBG').css({position:'absolute','z-index':9,'left':0,'top':0,'right':0}).show();
			}
//only 'jump to top' if we are partially down the page.  Using the header height as > means if a link in the header is clicked, we don't jump down to the content area. feels natural that way.
		if(infoObj.performJumpToTop && $(window).scrollTop() > $('header','#appView').height())	{
			$('html, body').animate({scrollTop : ($('header','#appView').length ? $('header','#appView').first().height() : 0)},500,function(){
				transitionThePage();
				});
			} //new page content loading. scroll to top.
		else	{
			transitionThePage();
			}

		}

	//Cart Messaging Responses.
	myApp.cmr.push(['chat.join',function(message){
		if(message.FROM == 'ADMIN')	{
			var $ui = myApp.ext.quickstart.a.showBuyerCMUI();
			$("[data-app-role='messageInput']",$ui).show();
			$("[data-app-role='messageHistory']",$ui).append("
"+message.FROM+" has joined the chat.<\/p>");
			$('.show4ActiveChat',$ui).show();
			$('.hide4ActiveChat',$ui).hide();
			}
		}]);

	//the default behavior for an itemAppend is to show the chat portion of the dialog. that's an undesired behavior from the buyer perspective (chat only works if admin is actively listening).
	myApp.cmr.push(['cart.itemAppend',function(message,$context)	{
		$("[data-app-role='messageHistory']",$context).append("
"+message.FROM+" has added item "+message.sku+" to the cart.<\/p>");
		}]);

	myApp.cmr.push(['goto',function(message,$context){
		var $history = $("[data-app-role='messageHistory']",$context);
		$P = $("
")
			.addClass('chat_post')
			.append(""+message.FROM+"<\/span> has sent over a "+(message.vars.pageType || "")+" link for you within this store. Click here<\/span> to view.")
			.on('click',function(){
				showContent(myApp.ext.quickstart.u.whatAmIFor(message.vars),message.vars);
				});
		$history.append($P);
		$history.parent().scrollTop($history.height());
		}]);

	myApp.ext.order_create.checkoutCompletes.push(function(vars,$checkout){
//append this to
		var cartContentsAsLinks = encodeURIComponent(myApp.ext.cco.u.cartContentsAsLinks(myApp.data[vars.datapointer].order));
		$("[data-app-role='thirdPartyContainer']",$checkout).append("
What next?

");
		$('.ocmTwitterComment',$checkout).click(function(){
			window.open('http://twitter.com/home?status='+cartContentsAsLinks,'twitter');
			_gaq.push(['_trackEvent','Checkout','User Event','Tweeted about order']);
			});
		//the fb code only works if an appID is set, so don't show banner if not present.
		if(myApp.u.thisNestedExists("zGlobals.thirdParty.facebook.appId") && typeof FB == 'object')	{
			$('.ocmFacebookComment',$checkout).click(function(){
				myApp.ext.quickstart.thirdParty.fb.postToWall(cartContentsAsLinks);
				_gaq.push(['_trackEvent','Checkout','User Event','FB message about order']);
				});
			}
		else	{$('.ocmFacebookComment').hide()}
		});
	}

//this will trigger the content to load on app init. so if you push refresh, you don't get a blank page.
//it'll also handle the old 'meta' uri params.
//this will trigger the content to load on app init. so if you push refresh, you don't get a blank page.
//it'll also handle the old 'meta' uri params.
myApp.router.appendInit({
	'type':'function',
	'route': function(v){
		return {'init':true} //returning anything but false triggers a match.
		},
	'callback':function(f,g){
		dump(" -> triggered callback for appendInit");
		g = g || {};
		if(g.uriParams.seoRequest){
			showContent(g.uriParams.pageType, g.uriParams);
			}
		else if(document.location.hash)	{
			myApp.u.dump('triggering handleHash');
			myApp.router.handleHashChange();
			}
		else	{
			//IE8 didn't like the shortcut to showContent here.
			myApp.ext.quickstart.a.showContent('homepage');
			}
		if(g.uriParams && g.uriParams.meta)	{
			myApp.ext.cco.calls.cartSet.init({'want/refer':infoObj.uriParams.meta,'cartID':_app.model.fetchCartID()},{},'passive');
			}
		if(g.uriParams && g.uriParams.meta_src)	{
			myApp.ext.cco.calls.cartSet.init({'want/refer_src':infoObj.uriParams.meta_src,'cartID':_app.model.fetchCartID()},{},'passive');
			}
		}
	});
<|endoftext|>


<commit_before>'use strict';

module.exports = encode;

var keys, keysNum, keysArr, dim, e,
    maxPrecision = 1e6;

var geometryTypes = {
    'Point': 0,
    'MultiPoint': 1,
    'LineString': 2,
    'MultiLineString': 3,
    'Polygon': 4,
    'MultiPolygon': 5,
    'GeometryCollection': 6
};

function encode(obj, pbf) {
    keys = {};
    keysArr = [];
    keysNum = 0;
    dim = 0;
    e = 1;

    analyze(obj);

    e = Math.min(e, maxPrecision);
    var precision = Math.ceil(Math.log(e) / Math.LN10);

    for (var i = 0; i < keysArr.length; i++) pbf.writeStringField(1, keysArr[i]);
    if (dim !== 2) pbf.writeVarintField(2, dim);
    if (precision !== 6) pbf.writeVarintField(3, precision);

    if (obj.type === 'FeatureCollection') pbf.writeMessage(4, writeFeatureCollection, obj);
    else if (obj.type === 'Feature') pbf.writeMessage(5, writeFeature, obj);
    else pbf.writeMessage(6, writeGeometry, obj);

    keys = null;

    return pbf.finish();
}

function analyze(obj) {
    var i, key;

    if (obj.type === 'FeatureCollection') {
        for (i = 0; i < obj.features.length; i++) analyze(obj.features[i]);

    } else if (obj.type === 'Feature') {
        if (obj.geometry !== null) analyze(obj.geometry);
        for (key in obj.properties) saveKey(key);

    } else if (obj.type === 'Point') analyzePoint(obj.coordinates);
    else if (obj.type === 'MultiPoint') analyzePoints(obj.coordinates);
    else if (obj.type === 'GeometryCollection') {
        for (i = 0; i < obj.geometries.length; i++) analyze(obj.geometries[i]);
    }
    else if (obj.type === 'LineString') analyzePoints(obj.coordinates);
    else if (obj.type === 'Polygon' || obj.type === 'MultiLineString') analyzeMultiLine(obj.coordinates);
    else if (obj.type === 'MultiPolygon') {
        for (i = 0; i < obj.coordinates.length; i++) analyzeMultiLine(obj.coordinates[i]);
    }

    for (key in obj) {
        if (!isSpecialKey(key, obj.type)) saveKey(key);
    }
}

function analyzeMultiLine(coords) {
    for (var i = 0; i < coords.length; i++) analyzePoints(coords[i]);
}

function analyzePoints(coords) {
    for (var i = 0; i < coords.length; i++) analyzePoint(coords[i]);
}

function analyzePoint(point) {
    dim = Math.max(dim, point.length);

    // find max precision
    for (var i = 0; i < point.length; i++) {
        while (Math.round(point[i] * e) / e !== point[i] && e < maxPrecision) e *= 10;
    }
}

function saveKey(key) {
    if (keys[key] === undefined) {
        keysArr.push(key);
        keys[key] = keysNum++;
    }
}

function writeFeatureCollection(obj, pbf) {
    for (var i = 0; i < obj.features.length; i++) {
        pbf.writeMessage(1, writeFeature, obj.features[i]);
    }
    writeProps(obj, pbf, true);
}

function writeFeature(feature, pbf) {
    if (feature.geometry !== null) pbf.writeMessage(1, writeGeometry, feature.geometry);

    if (feature.id !== undefined) {
        if (typeof feature.id === 'number' && feature.id % 1 === 0) pbf.writeSVarintField(12, feature.id);
        else pbf.writeStringField(11, feature.id);
    }

    if (feature.properties) writeProps(feature.properties, pbf);
    writeProps(feature, pbf, true);
}

function writeGeometry(geom, pbf) {
    pbf.writeVarintField(1, geometryTypes[geom.type]);

    var coords = geom.coordinates;

    if (geom.type === 'Point') writePoint(coords, pbf);
    else if (geom.type === 'MultiPoint') writeLine(coords, pbf, true);
    else if (geom.type === 'LineString') writeLine(coords, pbf);
    else if (geom.type === 'MultiLineString') writeMultiLine(coords, pbf);
    else if (geom.type === 'Polygon') writeMultiLine(coords, pbf, true);
    else if (geom.type === 'MultiPolygon') writeMultiPolygon(coords, pbf);
    else if (geom.type === 'GeometryCollection') {
        for (var i = 0; i < geom.geometries.length; i++) pbf.writeMessage(4, writeGeometry, geom.geometries[i]);
    }

    writeProps(geom, pbf, true);
}

function writeProps(props, pbf, isCustom) {
    var indexes = [],
        valueIndex = 0;

    for (var key in props) {
        if (isCustom && isSpecialKey(key, props.type)) {
            continue;
        }
        pbf.writeMessage(13, writeValue, props[key]);
        indexes.push(keys[key]);
        indexes.push(valueIndex++);
    }
    pbf.writePackedVarint(isCustom ? 15 : 14, indexes);
}

function writeValue(value, pbf) {
    var type = typeof value;

    if (type === 'string') pbf.writeStringField(1, value);
    else if (type === 'boolean') pbf.writeBooleanField(5, value);
    else if (type === 'object') pbf.writeStringField(6, JSON.stringify(value));
    else if (type === 'number') {
        if (value % 1 !== 0) pbf.writeDoubleField(2, value);
        else if (value >= 0) pbf.writeVarintField(3, value);
        else pbf.writeVarintField(4, -value);
    }
}

function writePoint(point, pbf) {
    var coords = [];
    for (var i = 0; i < dim; i++) coords.push(Math.round(point[i] * e));
    pbf.writePackedSVarint(3, coords);
}

function writeLine(line, pbf) {
    var coords = [];
    populateLine(coords, line);
    pbf.writePackedSVarint(3, coords);
}

function writeMultiLine(lines, pbf, closed) {
    var len = lines.length,
        i;
    if (len !== 1) {
        var lengths = [];
        for (i = 0; i < len; i++) lengths.push(lines[i].length - (closed ? 1 : 0));
        pbf.writePackedVarint(2, lengths);
        // TODO faster with custom writeMessage?
    }
    var coords = [];
    for (i = 0; i < len; i++) populateLine(coords, lines[i], closed);
    pbf.writePackedSVarint(3, coords);
}

function writeMultiPolygon(polygons, pbf) {
    var len = polygons.length,
        i, j;
    if (len !== 1 || polygons[0].length !== 1) {
        var lengths = [len];
        for (i = 0; i < len; i++) {
            lengths.push(polygons[i].length);
            for (j = 0; j < polygons[i].length; j++) lengths.push(polygons[i][j].length - 1);
        }
        pbf.writePackedVarint(2, lengths);
    }

    var coords = [];
    for (i = 0; i < len; i++) {
        for (j = 0; j < polygons[i].length; j++) populateLine(coords, polygons[i][j], true);
    }
    pbf.writePackedSVarint(3, coords);
}

function populateLine(coords, line, closed) {
    var i, j,
        len = line.length - (closed ? 1 : 0),
        sum = new Array(dim);
    for (j = 0; j < dim; j++) sum[j] = 0;
    for (i = 0; i < len; i++) {
        for (j = 0; j < dim; j++) {
            var n = Math.round(line[i][j] * e) - sum[j];
            coords.push(n);
            sum[j] += n;
        }
    }
}

function isSpecialKey(key, type) {
    if (key === 'type') return true;
    else if (type === 'FeatureCollection') {
        if (key === 'features') return true;
    } else if (type === 'Feature') {
        if (key === 'id' || key === 'properties' || key === 'geometry') return true;
    } else if (type === 'GeometryCollection') {
        if (key === 'geometries') return true;
    } else if (key === 'coordinates') return true;
    return false;
}
<commit_msg>FEAT: encode null property values as Value messages with the 'oneof' value_type deliberately unset (#95)<commit_after>'use strict';

module.exports = encode;

var keys, keysNum, keysArr, dim, e,
    maxPrecision = 1e6;

var geometryTypes = {
    'Point': 0,
    'MultiPoint': 1,
    'LineString': 2,
    'MultiLineString': 3,
    'Polygon': 4,
    'MultiPolygon': 5,
    'GeometryCollection': 6
};

function encode(obj, pbf) {
    keys = {};
    keysArr = [];
    keysNum = 0;
    dim = 0;
    e = 1;

    analyze(obj);

    e = Math.min(e, maxPrecision);
    var precision = Math.ceil(Math.log(e) / Math.LN10);

    for (var i = 0; i < keysArr.length; i++) pbf.writeStringField(1, keysArr[i]);
    if (dim !== 2) pbf.writeVarintField(2, dim);
    if (precision !== 6) pbf.writeVarintField(3, precision);

    if (obj.type === 'FeatureCollection') pbf.writeMessage(4, writeFeatureCollection, obj);
    else if (obj.type === 'Feature') pbf.writeMessage(5, writeFeature, obj);
    else pbf.writeMessage(6, writeGeometry, obj);

    keys = null;

    return pbf.finish();
}

function analyze(obj) {
    var i, key;

    if (obj.type === 'FeatureCollection') {
        for (i = 0; i < obj.features.length; i++) analyze(obj.features[i]);

    } else if (obj.type === 'Feature') {
        if (obj.geometry !== null) analyze(obj.geometry);
        for (key in obj.properties) saveKey(key);

    } else if (obj.type === 'Point') analyzePoint(obj.coordinates);
    else if (obj.type === 'MultiPoint') analyzePoints(obj.coordinates);
    else if (obj.type === 'GeometryCollection') {
        for (i = 0; i < obj.geometries.length; i++) analyze(obj.geometries[i]);
    }
    else if (obj.type === 'LineString') analyzePoints(obj.coordinates);
    else if (obj.type === 'Polygon' || obj.type === 'MultiLineString') analyzeMultiLine(obj.coordinates);
    else if (obj.type === 'MultiPolygon') {
        for (i = 0; i < obj.coordinates.length; i++) analyzeMultiLine(obj.coordinates[i]);
    }

    for (key in obj) {
        if (!isSpecialKey(key, obj.type)) saveKey(key);
    }
}

function analyzeMultiLine(coords) {
    for (var i = 0; i < coords.length; i++) analyzePoints(coords[i]);
}

function analyzePoints(coords) {
    for (var i = 0; i < coords.length; i++) analyzePoint(coords[i]);
}

function analyzePoint(point) {
    dim = Math.max(dim, point.length);

    // find max precision
    for (var i = 0; i < point.length; i++) {
        while (Math.round(point[i] * e) / e !== point[i] && e < maxPrecision) e *= 10;
    }
}

function saveKey(key) {
    if (keys[key] === undefined) {
        keysArr.push(key);
        keys[key] = keysNum++;
    }
}

function writeFeatureCollection(obj, pbf) {
    for (var i = 0; i < obj.features.length; i++) {
        pbf.writeMessage(1, writeFeature, obj.features[i]);
    }
    writeProps(obj, pbf, true);
}

function writeFeature(feature, pbf) {
    if (feature.geometry !== null) pbf.writeMessage(1, writeGeometry, feature.geometry);

    if (feature.id !== undefined) {
        if (typeof feature.id === 'number' && feature.id % 1 === 0) pbf.writeSVarintField(12, feature.id);
        else pbf.writeStringField(11, feature.id);
    }

    if (feature.properties) writeProps(feature.properties, pbf);
    writeProps(feature, pbf, true);
}

function writeGeometry(geom, pbf) {
    pbf.writeVarintField(1, geometryTypes[geom.type]);

    var coords = geom.coordinates;

    if (geom.type === 'Point') writePoint(coords, pbf);
    else if (geom.type === 'MultiPoint') writeLine(coords, pbf, true);
    else if (geom.type === 'LineString') writeLine(coords, pbf);
    else if (geom.type === 'MultiLineString') writeMultiLine(coords, pbf);
    else if (geom.type === 'Polygon') writeMultiLine(coords, pbf, true);
    else if (geom.type === 'MultiPolygon') writeMultiPolygon(coords, pbf);
    else if (geom.type === 'GeometryCollection') {
        for (var i = 0; i < geom.geometries.length; i++) pbf.writeMessage(4, writeGeometry, geom.geometries[i]);
    }

    writeProps(geom, pbf, true);
}

function writeProps(props, pbf, isCustom) {
    var indexes = [],
        valueIndex = 0;

    for (var key in props) {
        if (isCustom && isSpecialKey(key, props.type)) {
            continue;
        }
        pbf.writeMessage(13, writeValue, props[key]);
        indexes.push(keys[key]);
        indexes.push(valueIndex++);
    }
    pbf.writePackedVarint(isCustom ? 15 : 14, indexes);
}

function writeValue(value, pbf) {
    if (value === null) return;

    var type = typeof value;

    if (type === 'string') pbf.writeStringField(1, value);
    else if (type === 'boolean') pbf.writeBooleanField(5, value);
    else if (type === 'object') pbf.writeStringField(6, JSON.stringify(value));
    else if (type === 'number') {
        if (value % 1 !== 0) pbf.writeDoubleField(2, value);
        else if (value >= 0) pbf.writeVarintField(3, value);
        else pbf.writeVarintField(4, -value);
    }
}

function writePoint(point, pbf) {
    var coords = [];
    for (var i = 0; i < dim; i++) coords.push(Math.round(point[i] * e));
    pbf.writePackedSVarint(3, coords);
}

function writeLine(line, pbf) {
    var coords = [];
    populateLine(coords, line);
    pbf.writePackedSVarint(3, coords);
}

function writeMultiLine(lines, pbf, closed) {
    var len = lines.length,
        i;
    if (len !== 1) {
        var lengths = [];
        for (i = 0; i < len; i++) lengths.push(lines[i].length - (closed ? 1 : 0));
        pbf.writePackedVarint(2, lengths);
        // TODO faster with custom writeMessage?
    }
    var coords = [];
    for (i = 0; i < len; i++) populateLine(coords, lines[i], closed);
    pbf.writePackedSVarint(3, coords);
}

function writeMultiPolygon(polygons, pbf) {
    var len = polygons.length,
        i, j;
    if (len !== 1 || polygons[0].length !== 1) {
        var lengths = [len];
        for (i = 0; i < len; i++) {
            lengths.push(polygons[i].length);
            for (j = 0; j < polygons[i].length; j++) lengths.push(polygons[i][j].length - 1);
        }
        pbf.writePackedVarint(2, lengths);
    }

    var coords = [];
    for (i = 0; i < len; i++) {
        for (j = 0; j < polygons[i].length; j++) populateLine(coords, polygons[i][j], true);
    }
    pbf.writePackedSVarint(3, coords);
}

function populateLine(coords, line, closed) {
    var i, j,
        len = line.length - (closed ? 1 : 0),
        sum = new Array(dim);
    for (j = 0; j < dim; j++) sum[j] = 0;
    for (i = 0; i < len; i++) {
        for (j = 0; j < dim; j++) {
            var n = Math.round(line[i][j] * e) - sum[j];
            coords.push(n);
            sum[j] += n;
        }
    }
}

function isSpecialKey(key, type) {
    if (key === 'type') return true;
    else if (type === 'FeatureCollection') {
        if (key === 'features') return true;
    } else if (type === 'Feature') {
        if (key === 'id' || key === 'properties' || key === 'geometry') return true;
    } else if (type === 'GeometryCollection') {
        if (key === 'geometries') return true;
    } else if (key === 'coordinates') return true;
    return false;
}
<|endoftext|>


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

var common = require('../../tasks/util/common');
var getMockList = require('./assets/get_mock_list');
var getRequestOpts = require('./assets/get_image_request_options');
var getImagePaths = require('./assets/get_image_paths');

// packages inside the image server docker
var test = require('tape');
var request = require('request');
var gm = require('gm');

// pixel comparison tolerance
var TOLERANCE = 1e-6;

// wait time between each test batch
var BATCH_WAIT = 500;

// number of tests in each test batch
var BATCH_SIZE = 5;

// wait time between each test in test queue
var QUEUE_WAIT = 10;

/**
 *  Image pixel comparison test script.
 *
 *  Called by `tasks/test_image.sh in `npm run test-image`.
 *
 *  CLI arguments:
 *
 *  1. 'pattern' : glob determining which mock(s) are to be tested
 *  2. --queue : if sent, the image will be run in queue instead of in batch.
 *      Makes the test run significantly longer, but is recommended on weak hardware.
 *
 *  Examples:
 *
 *  Run all tests in batch:
 *
 *      npm run test-image
 *
 *  Run the 'contour_nolines' test:
 *
 *      npm run test-image -- contour_nolines
 *
 *  Run all gl3d image test in queue:
 *
 *      npm run test-image -- gl3d_* --queue
 *
 *
 */

var argv = minimist(process.argv.slice(2), {
    boolean: ['queue', 'filter', 'skip-flaky', 'just-flaky']
});

var allMock = false;
// If no pattern is provided, all mocks are compared
if(argv._.length === 0) {
    allMock = true;
    argv._.push('');
}

// Build list of mocks to compare
var allMockList = [];
argv._.forEach(function(pattern) {
    var mockList = getMockList(pattern);

    if(mockList.length === 0) {
        throw new Error('No mocks found with pattern ' + pattern);
    }

    allMockList = allMockList.concat(mockList);
});

// To get rid of duplicates
function unique(value, index, self) {
    return self.indexOf(value) === index;
}
allMockList = allMockList.filter(unique);

// Skip MathJax mocks that are tested using orca in noci_test
var MATHJAX_LIST = [
    'mathjax',
    'ternary-mathjax',
    'table_plain_birds',
    'table_wrapped_birds',
    'parcats_grid_subplots',
    'legend_mathjax_title_and_items',
    'table_latex_multitrace_scatter'
];
allMockList = allMockList.filter(function(e) {
    return MATHJAX_LIST.indexOf(e) === -1;
});

// filter out untestable mocks if no pattern is specified (ie. we're testing all mocks)
// or if flag '--filter' is provided
console.log('');
if(allMock || argv.filter) {
    console.log('Filtering out untestable mocks:');
    // Test cases:
    // - font-wishlist
    // - all mapbox
    // don't behave consistently from run-to-run and/or
    // machine-to-machine; skip over them for now.
    allMockList = allMockList.filter(function(mockName) {
        var cond = !(
            mockName === 'font-wishlist' ||
            mockName.indexOf('mapbox_') !== -1
        );
        if(!cond) console.log(' -', mockName);
        return cond;
    });
}

var FLAKY_LIST = [
    'treemap_coffee',
    'treemap_sunburst_marker_colors',
    'treemap_textposition',
    'treemap_with-without_values',
    'gl3d_directions-streamtube1',
];

console.log('');
if(argv['skip-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        var cond = FLAKY_LIST.indexOf(mockName) === -1;
        if(!cond) console.log('Skipping flaky mock', mockName);
        return cond;
    });
} else if(argv['just-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        return FLAKY_LIST.indexOf(mockName) !== -1;
    });
}

/* gl2d pointcloud and other non-regl gl2d mock(s)
 * must be tested first on in order to work;
 * sort them here.
 *
 * gl-shader appears to conflict with regl.
 * We suspect that the lone gl context on CircleCI is
 * having issues with dealing with the two different
 * program binding algorithm.
 *
 * The problem will be solved by switching all our
 * WebGL-based trace types to regl.
 *
 * More info here:
 * https://github.com/plotly/plotly.js/pull/1037
 */
function sortGl2dMockList(mockList) {
    var mockNames = ['gl2d_pointcloud-basic', 'gl2d_heatmapgl'];
    var pos = 0;

    mockNames.forEach(function(m) {
        var ind = mockList.indexOf(m);
        if(ind === -1) return;
        var tmp = mockList[pos];
        mockList[pos] = m;
        mockList[ind] = tmp;
        pos++;
    });
}

function runInBatch(mockList) {
    var running = 0;

    test('testing mocks in batch', function(t) {
        t.plan(mockList.length);

        for(var i = 0; i < mockList.length; i++) {
            run(mockList[i], t);
        }
    });

    function run(mockName, t) {
        if(running >= BATCH_SIZE) {
            setTimeout(function() {
                run(mockName, t);
            }, BATCH_WAIT);
            return;
        }
        running++;

        // throttle the number of tests running concurrently

        comparePixels(mockName, function(isEqual, mockName) {
            running--;
            t.ok(isEqual, mockName + ' should be pixel perfect');
        });
    }
}

function runInQueue(mockList) {
    var index = 0;

    test('testing mocks in queue', function(t) {
        t.plan(mockList.length);

        run(mockList[index], t);
    });

    function run(mockName, t) {
        comparePixels(mockName, function(isEqual, mockName) {
            t.ok(isEqual, mockName + ' should be pixel perfect');

            index++;
            if(index < mockList.length) {
                setTimeout(function() {
                    run(mockList[index], t);
                }, QUEUE_WAIT);
            }
        });
    }
}

function comparePixels(mockName, cb) {
    var requestOpts = getRequestOpts({ mockName: mockName });
    var imagePaths = getImagePaths(mockName);
    var saveImageStream = fs.createWriteStream(imagePaths.test);

    function log(msg) {
        process.stdout.write('Error for', mockName + ':', msg);
    }

    function checkImage() {
        // baseline image must be generated first
        if(!common.doesFileExist(imagePaths.baseline)) {
            var err = new Error('baseline image not found');
            return onEqualityCheck(err, false);
        }

        /*
         * N.B. The non-zero tolerance was added in
         * https://github.com/plotly/plotly.js/pull/243
         * where some legend mocks started generating different png outputs
         * on `npm run test-image` and `npm run test-image -- mock.json`.
         *
         * Note that the svg outputs for the problematic mocks were the same
         * and playing around with the batch size and timeout durations
         * did not seem to affect the results.
         *
         * With the above tolerance individual `npm run test-image` and
         * `npm run test-image -- mock.json` give the same result.
         *
         * Further investigation is needed.
         */

        var gmOpts = {
            file: imagePaths.diff,
            highlightColor: 'purple',
            tolerance: TOLERANCE
        };

        gm.compare(
            imagePaths.test,
            imagePaths.baseline,
            gmOpts,
            onEqualityCheck
        );
    }

    function onEqualityCheck(err, isEqual) {
        if(err) {
            common.touch(imagePaths.diff);
            log(err);
            return cb(false, mockName);
        }
        if(isEqual) {
            fs.unlinkSync(imagePaths.diff);
        }

        cb(isEqual, mockName);
    }

    // 525 means a plotly.js error
    function onResponse(response) {
        if(+response.statusCode === 525) {
            log('plotly.js error');
            return cb(false, mockName);
        }
    }

    // this catches connection errors
    // e.g. when the image server blows up
    function onError(err) {
        log(err);
        return cb(false, mockName);
    }

    request(requestOpts)
        .on('error', onError)
        .on('response', onResponse)
        .pipe(saveImageStream)
        .on('close', checkImage);
}

sortGl2dMockList(allMockList);
console.log('');

// main
if(argv.queue) {
    runInQueue(allMockList);
} else {
    runInBatch(allMockList);
}
<commit_msg>do not keep new baseline when compare pass<commit_after>var fs = require('fs');
var minimist = require('minimist');

var common = require('../../tasks/util/common');
var getMockList = require('./assets/get_mock_list');
var getRequestOpts = require('./assets/get_image_request_options');
var getImagePaths = require('./assets/get_image_paths');

// packages inside the image server docker
var test = require('tape');
var request = require('request');
var gm = require('gm');

// pixel comparison tolerance
var TOLERANCE = 1e-6;

// wait time between each test batch
var BATCH_WAIT = 500;

// number of tests in each test batch
var BATCH_SIZE = 5;

// wait time between each test in test queue
var QUEUE_WAIT = 10;

/**
 *  Image pixel comparison test script.
 *
 *  Called by `tasks/test_image.sh in `npm run test-image`.
 *
 *  CLI arguments:
 *
 *  1. 'pattern' : glob determining which mock(s) are to be tested
 *  2. --queue : if sent, the image will be run in queue instead of in batch.
 *      Makes the test run significantly longer, but is recommended on weak hardware.
 *
 *  Examples:
 *
 *  Run all tests in batch:
 *
 *      npm run test-image
 *
 *  Run the 'contour_nolines' test:
 *
 *      npm run test-image -- contour_nolines
 *
 *  Run all gl3d image test in queue:
 *
 *      npm run test-image -- gl3d_* --queue
 *
 *
 */

var argv = minimist(process.argv.slice(2), {
    boolean: ['queue', 'filter', 'skip-flaky', 'just-flaky']
});

var allMock = false;
// If no pattern is provided, all mocks are compared
if(argv._.length === 0) {
    allMock = true;
    argv._.push('');
}

// Build list of mocks to compare
var allMockList = [];
argv._.forEach(function(pattern) {
    var mockList = getMockList(pattern);

    if(mockList.length === 0) {
        throw new Error('No mocks found with pattern ' + pattern);
    }

    allMockList = allMockList.concat(mockList);
});

// To get rid of duplicates
function unique(value, index, self) {
    return self.indexOf(value) === index;
}
allMockList = allMockList.filter(unique);

// Skip MathJax mocks that are tested using orca in noci_test
var MATHJAX_LIST = [
    'mathjax',
    'ternary-mathjax',
    'table_plain_birds',
    'table_wrapped_birds',
    'parcats_grid_subplots',
    'legend_mathjax_title_and_items',
    'table_latex_multitrace_scatter'
];
allMockList = allMockList.filter(function(e) {
    return MATHJAX_LIST.indexOf(e) === -1;
});

// filter out untestable mocks if no pattern is specified (ie. we're testing all mocks)
// or if flag '--filter' is provided
console.log('');
if(allMock || argv.filter) {
    console.log('Filtering out untestable mocks:');
    // Test cases:
    // - font-wishlist
    // - all mapbox
    // don't behave consistently from run-to-run and/or
    // machine-to-machine; skip over them for now.
    allMockList = allMockList.filter(function(mockName) {
        var cond = !(
            mockName === 'font-wishlist' ||
            mockName.indexOf('mapbox_') !== -1
        );
        if(!cond) console.log(' -', mockName);
        return cond;
    });
}

var FLAKY_LIST = [
    'treemap_coffee',
    'treemap_sunburst_marker_colors',
    'treemap_textposition',
    'treemap_with-without_values',
    'gl3d_directions-streamtube1',
];

console.log('');
if(argv['skip-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        var cond = FLAKY_LIST.indexOf(mockName) === -1;
        if(!cond) console.log('Skipping flaky mock', mockName);
        return cond;
    });
} else if(argv['just-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        return FLAKY_LIST.indexOf(mockName) !== -1;
    });
}

/* gl2d pointcloud and other non-regl gl2d mock(s)
 * must be tested first on in order to work;
 * sort them here.
 *
 * gl-shader appears to conflict with regl.
 * We suspect that the lone gl context on CircleCI is
 * having issues with dealing with the two different
 * program binding algorithm.
 *
 * The problem will be solved by switching all our
 * WebGL-based trace types to regl.
 *
 * More info here:
 * https://github.com/plotly/plotly.js/pull/1037
 */
function sortGl2dMockList(mockList) {
    var mockNames = ['gl2d_pointcloud-basic', 'gl2d_heatmapgl'];
    var pos = 0;

    mockNames.forEach(function(m) {
        var ind = mockList.indexOf(m);
        if(ind === -1) return;
        var tmp = mockList[pos];
        mockList[pos] = m;
        mockList[ind] = tmp;
        pos++;
    });
}

function runInBatch(mockList) {
    var running = 0;

    test('testing mocks in batch', function(t) {
        t.plan(mockList.length);

        for(var i = 0; i < mockList.length; i++) {
            run(mockList[i], t);
        }
    });

    function run(mockName, t) {
        if(running >= BATCH_SIZE) {
            setTimeout(function() {
                run(mockName, t);
            }, BATCH_WAIT);
            return;
        }
        running++;

        // throttle the number of tests running concurrently

        comparePixels(mockName, function(isEqual, mockName) {
            running--;
            t.ok(isEqual, mockName + ' should be pixel perfect');
        });
    }
}

function runInQueue(mockList) {
    var index = 0;

    test('testing mocks in queue', function(t) {
        t.plan(mockList.length);

        run(mockList[index], t);
    });

    function run(mockName, t) {
        comparePixels(mockName, function(isEqual, mockName) {
            t.ok(isEqual, mockName + ' should be pixel perfect');

            index++;
            if(index < mockList.length) {
                setTimeout(function() {
                    run(mockList[index], t);
                }, QUEUE_WAIT);
            }
        });
    }
}

function comparePixels(mockName, cb) {
    var requestOpts = getRequestOpts({ mockName: mockName });
    var imagePaths = getImagePaths(mockName);
    var saveImageStream = fs.createWriteStream(imagePaths.test);

    function log(msg) {
        process.stdout.write('Error for', mockName + ':', msg);
    }

    function checkImage() {
        // baseline image must be generated first
        if(!common.doesFileExist(imagePaths.baseline)) {
            var err = new Error('baseline image not found');
            return onEqualityCheck(err, false);
        }

        /*
         * N.B. The non-zero tolerance was added in
         * https://github.com/plotly/plotly.js/pull/243
         * where some legend mocks started generating different png outputs
         * on `npm run test-image` and `npm run test-image -- mock.json`.
         *
         * Note that the svg outputs for the problematic mocks were the same
         * and playing around with the batch size and timeout durations
         * did not seem to affect the results.
         *
         * With the above tolerance individual `npm run test-image` and
         * `npm run test-image -- mock.json` give the same result.
         *
         * Further investigation is needed.
         */

        var gmOpts = {
            file: imagePaths.diff,
            highlightColor: 'purple',
            tolerance: TOLERANCE
        };

        gm.compare(
            imagePaths.test,
            imagePaths.baseline,
            gmOpts,
            onEqualityCheck
        );
    }

    function onEqualityCheck(err, isEqual) {
        if(err) {
            common.touch(imagePaths.diff);
            log(err);
            return cb(false, mockName);
        }
        if(isEqual) {
            fs.unlinkSync(imagePaths.diff);
            fs.unlinkSync(imagePaths.test);
        }

        cb(isEqual, mockName);
    }

    // 525 means a plotly.js error
    function onResponse(response) {
        if(+response.statusCode === 525) {
            log('plotly.js error');
            return cb(false, mockName);
        }
    }

    // this catches connection errors
    // e.g. when the image server blows up
    function onError(err) {
        log(err);
        return cb(false, mockName);
    }

    request(requestOpts)
        .on('error', onError)
        .on('response', onResponse)
        .pipe(saveImageStream)
        .on('close', checkImage);
}

sortGl2dMockList(allMockList);
console.log('');

// main
if(argv.queue) {
    runInQueue(allMockList);
} else {
    runInBatch(allMockList);
}
<|endoftext|>


<commit_before>'use strict'
const path = require('path')
const express = require('express')
const webpack = require('webpack')

const webpackConfig = require('./webpack.dev')
const config = require('./config')

const app = express()

const compiler = webpack(webpackConfig)

const devMiddleWare = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  stats: {
    colors: true,
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false
  }
})
app.use(devMiddleWare)
app.use(require('webpack-hot-middleware')(compiler))

app.get('*', (req, res) => {
  const fs = devMiddleWare.fileSystem
  devMiddleWare.waitUntilValid(() => {
    const html = fs.readFileSync(path.join(webpackConfig.output.path, '../index.html'))
    res.end(html)
  })
})

app.listen(config.port, () => {
  console.log(`Listening at http://localhost:${config.port}`)
})
<commit_msg>support hot reload<commit_after>'use strict'
const path = require('path')
const express = require('express')
const webpack = require('webpack')

const webpackConfig = require('./webpack.dev')
const config = require('./config')

const app = express()

webpackConfig.entry.client = [
  'webpack-hot-middleware/client',
  webpackConfig.entry.client
]

const compiler = webpack(webpackConfig)

const devMiddleWare = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  stats: {
    colors: true,
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false
  }
})
app.use(devMiddleWare)
app.use(require('webpack-hot-middleware')(compiler))

app.get('*', (req, res) => {
  const fs = devMiddleWare.fileSystem
  devMiddleWare.waitUntilValid(() => {
    const html = fs.readFileSync(path.join(webpackConfig.output.path, '../index.html'))
    res.end(html)
  })
})

app.listen(config.port, () => {
  console.log(`Listening at http://localhost:${config.port}`)
})
<|endoftext|>


<commit_before>(function(){

    angular.module('Energy', [])
        .controller('EnergyCtrl', EnergyCtrl)
        .service('EnergyService', EnergyService)

    /**
     * Model, store everything here
     */
    function EnergyService(){

        var self = this;

        self.energyExpendatures = [
            {name: '1km Bike ride', energy: 146400, type:'exp'},
            {name: '1km Car drive', energy: 2.72e6, type:'exp'},
            {name: 'Shot by air gun', energy: 10, type:'kinetic'},
            {name: 'Shot by hunting rifle', energy: 1000, type:'kinetic'},
            {name: 'Human punch', energy: 150, type:'exp'},
            {name: '1day of human life', energy: 10e6, type:'exp'},
            {name: '10min hot shower', energy: 20e6, type:'exp'},
            {name: 'World energy consumption by second', energy: 1.6288038e13}

        ];

        self.energyStores = [
            {name: 'Litre gasoline', energy: 33.92e6, type:'store'},
            {name: '1litre water at 10m', energy: 0.0001e6, type:'store'},
            {name: '1kg compressed air', energy: 0.5e6, type:'store'},
            {name: 'Anti matter', energy: 1.8e11, type:'store'},
            {name: '1kg Human Fat', energy: 37e6, type:'store'},
            {name: 'AA Battery', energy: 0.0154e6, type:'store'},
            {name: 'Car Battery', energy: 2.6e6, type:'store'},
            {name: 'Mars bar', energy: 963000, type:'store'},
            {name: 'Banana', energy: 418000, type:'store'}
        ];

        /**
         * Other ideas
         * Sunlight in seconds
         * Whole world energy consumption per second
         * Batteries
         * Fusion
         * Gasoline
         * Running
         * Lifting
         * Atom bomb
         * e=mc2
         * house hold
         * toasting bread
         * washing clothes
         * Industrial size wind turbine
         * Explosions
         * Burning a tree
         * Burning a candle
         * A smile
         * Sex
         * Shouting at someone
         * Shower
         * Cooking a liter of water
         * A punch
         * A Kick
         * Bullet
         */

        self.allEnergy = self.energyExpendatures.concat(self.energyStores);
    }

    /**
     *
     * @param $scope
     * @param EnergyService
     */
    function EnergyCtrl($scope, EnergyService){
        $scope.EnergyService = EnergyService;

        // after we've selected a thing, we need to trigger the recalculation

        $scope.recalc = function(){

            var comparable = $scope.selectedCompare;
            var comparee = $scope.selectedComparee;

            var ratio = Math.round((comparable.energy / comparee.energy)*100)/100;

            // Take the first thing. compare it to the other thing
            // How many bananas are there in a mars bar?
            if(ratio < 1){
                ratio = ratio * 100 +'%';
            }else{
                ratio += 'x'
            }
            $scope.ratio = ratio;
        }
    }

})();<commit_msg>More stuff<commit_after>(function(){

    angular.module('Energy', [])
        .controller('EnergyCtrl', EnergyCtrl)
        .service('EnergyService', EnergyService)

    /**
     * Model, store everything here
     */
    function EnergyService(){

        var self = this;

        self.energyExpendatures = [
            {name: '1km Bike ride', energy: 146400, type:'exp'},
            {name: '1km Car drive', energy: 2.72e6, type:'exp'},
            {name: 'Impact by air gun', energy: 10, type:'kinetic'},
            {name: 'Impact by hunting rifle', energy: 1000, type:'kinetic'},
            {name: 'Human punch', energy: 150, type:'exp'},
            {name: '1day of human life', energy: 10e6, type:'exp'},
            {name: '10min hot shower', energy: 20e6, type:'exp'},
            {name: '1sec Earths solar absorption', energy: 0.1221e18, type:'exp'},
            {name: '1sec World energy consumption', energy: 1.6288038e13}

        ];

        self.energyStores = [
            {name: '1litre Gasoline', energy: 33.92e6, type:'store'},
            {name: '1kg wood for burning', energy: 10e6, type:'store'},
            {name: '1litre Water at 10m', energy: 0.0001e6, type:'store'},
            {name: '1kg Compressed air', energy: 0.5e6, type:'store'},
            {name: '1kg Anti matter', energy: 1.8e11, type:'store'},
            {name: '1kg Human Fat', energy: 37e6, type:'store'},
            {name: 'AA Battery', energy: 0.0154e6, type:'store'},
            {name: 'Car Battery', energy: 2.6e6, type:'store'},
            {name: 'Mars bar', energy: 963000, type:'store'},
            {name: 'Banana', energy: 418000, type:'store'}
        ];

        /**
         * Other ideas
         * Sunlight in seconds
         * Whole world energy consumption per second
         * Batteries
         * Fusion
         * Gasoline
         * Running
         * Lifting
         * Atom bomb
         * e=mc2
         * house hold
         * toasting bread
         * washing clothes
         * Industrial size wind turbine
         * Explosions
         * Burning a tree
         * Burning a candle
         * A smile
         * Sex
         * Shouting at someone
         * Shower
         * Cooking a liter of water
         * A punch
         * A Kick
         * Bullet
         */

        self.allEnergy = self.energyExpendatures.concat(self.energyStores);
    }

    /**
     *
     * @param $scope
     * @param EnergyService
     */
    function EnergyCtrl($scope, EnergyService){
        $scope.EnergyService = EnergyService;

        // after we've selected a thing, we need to trigger the recalculation

        $scope.recalc = function(){

            var comparable = $scope.selectedCompare;
            var comparee = $scope.selectedComparee;

            var ratio = Math.round((comparable.energy / comparee.energy)*100)/100;

            // Take the first thing. compare it to the other thing
            // How many bananas are there in a mars bar?
            if(ratio < 1){
                ratio = ratio * 100 +'%';
            }else{
                ratio += 'x'
            }
            $scope.ratio = ratio;
        }
    }

})();<|endoftext|>


<commit_before><commit_msg>Update portal_switcher.js<commit_after><|endoftext|>


<commit_before>
import { fireEvent } from 'c/pubsubNoPageRef';
import { getNamespace, isFunction, isNull } from 'c/utilCommon';

import PAYMENT_AUTHORIZATION_TOKEN_FIELD from
        '@salesforce/schema/DataImport__c.Payment_Authorization_Token__c';
import tokenRequestTimedOut from '@salesforce/label/c.gePaymentRequestTimedOut';

/***
* @description Visualforce page used to handle the payment services credit card tokenization request
*/
const TOKENIZE_CARD_PAGE_NAME = 'GE_TokenizeCard';

/***
* @description Event action sent to the Visualforce page to request the token
*/
const TOKENIZE_EVENT_ACTION = 'createToken';

/***
* @description Max number of ms to wait for the response containing a token or an error
*/
const TOKENIZE_TIMEOUT_MS = 10000;

/***
* @description Payment services Elevate credit card tokenization service
*/
class psElevateTokenHandler {
    /***
    * @description Custom labels
    */
    labels = Object.freeze({
        tokenRequestTimedOut
    });

    _visualforceOriginUrls;
    _visualforceOrigin;

    /***
    * @description Returns credit card tokenization Visualforce page URL
    */
    getTokenizeCardPageURL() {
        return this.currentNamespace
            ? `/apex/${this.currentNamespace}__${TOKENIZE_CARD_PAGE_NAME}`
            : `/apex/${TOKENIZE_CARD_PAGE_NAME}`;
    }

    buildVisualForceOriginURLs(domainInfo) {
        let url = `https://${domainInfo.orgDomain}--c.visualforce.com`;
        let alternateUrl = `https://${domainInfo.orgDomain}--c.${domainInfo.podName}.visual.force.com`;
        let productionEnhancedUrl = `https://${domainInfo.orgDomain}--c.vf.force.com`;
        let sandboxEnhancedUrl =  `https://${domainInfo.orgDomain}--c.sandbox.vf.force.com`;

        const originURLS = [
            {value: url},
            {value: alternateUrl},
            {value: productionEnhancedUrl},
            {value: sandboxEnhancedUrl}
        ];

        if (this.currentNamespace) {
            return this.convertToNamespacedURLs(originURLS);
        }
        return originURLS;
    }

    convertToNamespacedURLs(originUrls) {
        let convertedUrls = [];
        originUrls.forEach(url => {
            convertedUrls.push(
                {value : url.value.replace('--c', `--${this.currentNamespace}`)});
        })
        return convertedUrls;
    }

    /***
    * @description Builds the Visualforce origin url that we need in order to
    * make sure we're only listening for messages from the correct source.
    */
    setVisualforceOriginURLs(domainInfo) {
        if (isNull(domainInfo)) {
            return;
        }

        this._visualforceOriginUrls =
            this.buildVisualForceOriginURLs(domainInfo);
    }

    /***
    * @description Returns the NPSP namespace (if any)
    */
    get currentNamespace() {
        return getNamespace(PAYMENT_AUTHORIZATION_TOKEN_FIELD.fieldApiName);
    }

    /***
    * @description Dispatches the application event when the Elevate credit card iframe
    * is displayed or hidden
    */
    dispatchApplicationEvent(eventName, payload) {
        fireEvent(null, eventName, payload);
    }

    /***
    * @description Listens for a message from the Visualforce iframe.
    * Rejects any messages from an unknown origin.
    */
    registerPostMessageListener(component) {
        let self = this;

        window.onmessage = async function (event) {
            if (self._visualforceOriginUrls) {
                self._visualforceOrigin = self._visualforceOriginUrls.find(
                    origin => event.origin === origin.value
                );
                self._visualforceOrigin =
                    self._visualforceOrigin !== undefined ?
                        self._visualforceOrigin.value : undefined;
                if (self._visualforceOrigin !== undefined) {
                    const message = JSON.parse(event.data);
                    component.handleMessage(message);
                }
            }
        }
    }

    /***
    * @description Handles messages received from Visualforce page.
    * @param message Message received from the iframe
    */
    handleMessage(message) {
        if (message.error || message.token) {
            if (isFunction(this.tokenCallback)) {
                this.tokenCallback(message);
            }
        }
    }

    /***
    * @description Method sends a message to the visualforce page iframe requesting a token.
    * This request response is found and handled in the registerPostMessageListener().
    * @param iframe The payment services iframe displayed within the credit card widget LWC
    * @param cardholderName The cardholder name
    * @param handleError An error handler function
    * @param resolveToken Function (if any) called when a token is generated
    * @return Promise A token promise
    */
    requestToken(iframe, cardholderName, handleError, resolveToken) {
        if (isNull(iframe)) {
            return;
        }

        const tokenPromise = new Promise((resolve, reject) => {

            const timer = setTimeout(() =>
                reject(handleError({
                    error: this.labels.tokenRequestTimedOut,
                    isObject: false
                })),
                TOKENIZE_TIMEOUT_MS
            );

            this.tokenCallback = message => {
                clearTimeout(timer);

                if (message.error) {
                    reject(handleError(message));

                } else if (message.token) {
                    if (resolveToken) {
                        resolve(resolveToken(message.token));
                    } else {
                        resolve(message.token);
                    }
                }
            };

        });

        iframe.contentWindow.postMessage(
            {
                action: TOKENIZE_EVENT_ACTION,
                nameOnCard: cardholderName
            },
            this._visualforceOrigin
        );

        return tokenPromise;
    }
}

/**
 * A new instance of the Elevate tokenization handler
 */
export default new psElevateTokenHandler();
<commit_msg>Refactored onMessage function<commit_after>
import { fireEvent } from 'c/pubsubNoPageRef';
import { getNamespace, isFunction, isNull } from 'c/utilCommon';

import PAYMENT_AUTHORIZATION_TOKEN_FIELD from
        '@salesforce/schema/DataImport__c.Payment_Authorization_Token__c';
import tokenRequestTimedOut from '@salesforce/label/c.gePaymentRequestTimedOut';

/***
* @description Visualforce page used to handle the payment services credit card tokenization request
*/
const TOKENIZE_CARD_PAGE_NAME = 'GE_TokenizeCard';

/***
* @description Event action sent to the Visualforce page to request the token
*/
const TOKENIZE_EVENT_ACTION = 'createToken';

/***
* @description Max number of ms to wait for the response containing a token or an error
*/
const TOKENIZE_TIMEOUT_MS = 10000;

/***
* @description Payment services Elevate credit card tokenization service
*/
class psElevateTokenHandler {
    /***
    * @description Custom labels
    */
    labels = Object.freeze({
        tokenRequestTimedOut
    });

    _visualforceOriginUrls;
    _visualforceOrigin;

    /***
    * @description Returns credit card tokenization Visualforce page URL
    */
    getTokenizeCardPageURL() {
        return this.currentNamespace
            ? `/apex/${this.currentNamespace}__${TOKENIZE_CARD_PAGE_NAME}`
            : `/apex/${TOKENIZE_CARD_PAGE_NAME}`;
    }

    buildVisualForceOriginURLs(domainInfo) {
        let url = `https://${domainInfo.orgDomain}--c.visualforce.com`;
        let alternateUrl = `https://${domainInfo.orgDomain}--c.${domainInfo.podName}.visual.force.com`;
        let productionEnhancedUrl = `https://${domainInfo.orgDomain}--c.vf.force.com`;
        let sandboxEnhancedUrl =  `https://${domainInfo.orgDomain}--c.sandbox.vf.force.com`;

        const originURLS = [
            {value: url},
            {value: alternateUrl},
            {value: productionEnhancedUrl},
            {value: sandboxEnhancedUrl}
        ];

        if (this.currentNamespace) {
            return this.convertToNamespacedURLs(originURLS);
        }
        return originURLS;
    }

    convertToNamespacedURLs(originUrls) {
        let convertedUrls = [];
        originUrls.forEach(url => {
            convertedUrls.push(
                {value : url.value.replace('--c', `--${this.currentNamespace}`)});
        })
        return convertedUrls;
    }

    /***
    * @description Builds the Visualforce origin url that we need in order to
    * make sure we're only listening for messages from the correct source.
    */
    setVisualforceOriginURLs(domainInfo) {
        if (isNull(domainInfo)) {
            return;
        }

        this._visualforceOriginUrls =
            this.buildVisualForceOriginURLs(domainInfo);
    }

    /***
    * @description Returns the NPSP namespace (if any)
    */
    get currentNamespace() {
        return getNamespace(PAYMENT_AUTHORIZATION_TOKEN_FIELD.fieldApiName);
    }

    /***
    * @description Dispatches the application event when the Elevate credit card iframe
    * is displayed or hidden
    */
    dispatchApplicationEvent(eventName, payload) {
        fireEvent(null, eventName, payload);
    }

    /***
    * @description Listens for a message from the Visualforce iframe.
    * Rejects any messages from an unknown origin.
    */
    registerPostMessageListener(component) {
        let self = this;

        window.onmessage = async function (event) {
            if (self.shouldHandleMessage(event)) {
                const message = JSON.parse(event.data);
                component.handleMessage(message);
            }
        }
    }

    shouldHandleMessage (event) {
        this._visualforceOrigin = this._visualforceOriginUrls.find(
            origin => event.origin === origin.value
        );
        this._visualforceOrigin =
            this._visualforceOrigin !== undefined ?
                this._visualforceOrigin.value : undefined;
        return this._visualforceOrigin !== undefined;
    }

    /***
    * @description Handles messages received from Visualforce page.
    * @param message Message received from the iframe
    */
    handleMessage(message) {
        if (message.error || message.token) {
            if (isFunction(this.tokenCallback)) {
                this.tokenCallback(message);
            }
        }
    }

    /***
    * @description Method sends a message to the visualforce page iframe requesting a token.
    * This request response is found and handled in the registerPostMessageListener().
    * @param iframe The payment services iframe displayed within the credit card widget LWC
    * @param cardholderName The cardholder name
    * @param handleError An error handler function
    * @param resolveToken Function (if any) called when a token is generated
    * @return Promise A token promise
    */
    requestToken(iframe, cardholderName, handleError, resolveToken) {
        if (isNull(iframe)) {
            return;
        }

        const tokenPromise = new Promise((resolve, reject) => {

            const timer = setTimeout(() =>
                reject(handleError({
                    error: this.labels.tokenRequestTimedOut,
                    isObject: false
                })),
                TOKENIZE_TIMEOUT_MS
            );

            this.tokenCallback = message => {
                clearTimeout(timer);

                if (message.error) {
                    reject(handleError(message));

                } else if (message.token) {
                    if (resolveToken) {
                        resolve(resolveToken(message.token));
                    } else {
                        resolve(message.token);
                    }
                }
            };

        });

        iframe.contentWindow.postMessage(
            {
                action: TOKENIZE_EVENT_ACTION,
                nameOnCard: cardholderName
            },
            this._visualforceOrigin
        );

        return tokenPromise;
    }
}

/**
 * A new instance of the Elevate tokenization handler
 */
export default new psElevateTokenHandler();
<|endoftext|>