<commit_before>/*
 *	Initial setup
 */

"use strict" // Otherwise we're stuck with ES5

const LOCAL_HOST = 3000;

// Variables required for Markov Chain generation
var input = [];
var firstWords = [];
var currentWord = "";

// Variables required for bottom
var currentTopic;
var currentToken;

// Set up modules
var express = require("express");
var request = require("request");
var bodyParser = require("body-parser");
var cheerio = require('cheerio');
var client = require('redis').createClient(process.env.REDIS_URL);

// Set up app
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.cookieJar = request.jar(); // Global cookie store
app.cachedData = { "post": "" }; // Basic format for JSON response
app.loggedIn = false; // Indicates whether app has login cookie for ETI

// Set up routing for API
var routes = require("./routes/routes.js")(app);

// Set up server
var server = app.listen(process.env.PORT || LOCAL_HOST, () => {
	console.log(process.env.USERNAME + "-bot is sentient and ready to shitpost");
});

/**
	*		Redis db handler
	*/

client.on("connect", () => {
		// Attempt to get "quotes" array from Redis
		client.lrange("quotes", 0, -1, (error, items) => {
			if (error) {
				// TODO: Store emergency vesper.txt somewhere in repo and use that instead
				throw error;
			}
			else {
				input = items;
				app.generateMarkovChain();
			}
		});
});

/**
  * Markov chain methods
	*/

app.generateMarkovChain = function(shouldReturn) {
	var output = [];

	app.createArrays();

	// Pick random first word to start with
	currentWord = firstWords[Math.floor((Math.random() * firstWords.length))];

	for (let i = 0, len = app.getPostLength(); i < len; i++) {
		output.push(currentWord);

		var nextWord = app.generateNextWord();

		if (nextWord) {
			currentWord = nextWord;
		}
		else {
			currentWord = "";
		}
	}

	app.cachedData.post = output.join(" ").trim();

	if (shouldReturn) {
		return app.cachedData.post;
	}
};

app.getPostLength = function() {
	return input[Math.floor((Math.random() * input.length))].length;
};

app.createArrays = function() {
	// Create array containing first word of each sentence
	for (let i = 0, len = input.length; i < len; i++) {
		let sentence = input[i];
		let firstWord = sentence.split(" ")[0];
		firstWords.push(firstWord);
	}
};

app.generateNextWord = function() {
	var tempArray = [];
	// Iterate over input array to find instances of current word
	for (let i = 0, len = input.length - 1; i < len; i++) {
		let sentenceToCheck = input[i].split(" ");

		for (let j = 0, len = sentenceToCheck.length; j < len; j++) {
			let wordToCheck = sentenceToCheck[j];
			// create array of words that are likely to follow current word
			if (wordToCheck === currentWord) {
				// Make sure that item at index i + 1 exists
				if (j < len - 1) {
					let nextWord = sentenceToCheck[j + 1];
					tempArray.push(nextWord);
				}
				else {
					// reached end of array - do nothing
				}
			}
		}
	}

	let rnd = Math.floor((Math.random() * tempArray.length));

	if (tempArray[rnd]) {
		return tempArray[rnd];
	}

	else {
		return;
	}
};

/**
  *		Method which allows us to add new quotes from pastebin raw links
  */

app.addNewQuotes = function(url) {
	request.get(url, ((error, response, body) => {
		if (!error && response.statusCode == 200) {
			// Ignore blank lines,
			var quotes = body.split("\r\n").filter((line) => line && line.indexOf("<") == -1 && line.charAt(0) !== "<");

			client.lrange("quotes", 0, -1, (error, items) => {
				if (error) {
					throw error;
				}
				else {
					// Update local & disk cache
					input = input.concat(quotes);
					client.rpush.apply(client, ["quotes"].concat(quotes).concat(() => { console.log("Quotes stored to redis") }));
				}
			});

		}

	}));
};

/**
  *		Log into ETI using environment vars as credentials
  */

app.loginToBlueSite = function() {
	const LOGIN_URL = "https://endoftheinter.net/";
	const formData = { b: process.env.USERNAME, p: process.env.PASSWORD };

	request.post({

		headers: {"content-type": "application/x-www-form-urlencoded"},
		url: LOGIN_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			// After successful login, ETI will attempt to redirect you to homepage
			if (!error && response.statusCode === 302) {
					app.getTopicList();
			}

			else {
				console.log("Login failed.");
				throw error;
			}
	});
};

app.getTopicList = function() {
	var LUE_TOPICS = "https://boards.endoftheinter.net/topics/LUE";

	request({

		url: LUE_TOPICS,
		jar: app.cookieJar

	}
	, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			try {
				var $ = cheerio.load(body);
				// Pick random topic to pester
				var anchors = $('td.oh div.fl a').get();
				console.log("anchors length: " + anchors.length);

				var anchor = topics[Math.floor(Math.random() * 49) + 1];
				console.log("anchor: " + anchor.attr('href'));

				var href = topic.attr('href');

				app.getMessageList(href);

			} catch (e) {

				console.log("Error while parsing topic list:");
				throw e;
			}
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.getMessageList = function(url) {

	request({

		url: url,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			var $ = cheerio.load(body);
			// Can't make POST requests without the value of this token, scraped from quickpost area
			currentToken = $('input[name="h"]').value();
			app.contributeToDiscussion();
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.contributeToDiscussion = function() {
	const QUICKPOST_URL = "http://boards.endoftheinter.net/async-post.php";

	var formData = {};
			formData.topic = currentTopic;
			formData.h = currentToken;
			formData.message = app.generateMarkovChain(true); // Pass true to get return value
			formData.submit = "Post Message";

	request.post({

		url: QUICKPOST_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			if (!error && response.statusCode === 200) {
				console.log("Post successful");
			}

			else {
				console.log("Post unsuccessful: code ", response.statusCode);
				throw error;
			}

	});
};<commit_msg>Maybe<commit_after>/*
 *	Initial setup
 */

"use strict" // Otherwise we're stuck with ES5

const LOCAL_HOST = 3000;

// Variables required for Markov Chain generation
var input = [];
var firstWords = [];
var currentWord = "";

// Variables required for bottom
var currentTopic;
var currentToken;

// Set up modules
var express = require("express");
var request = require("request");
var bodyParser = require("body-parser");
var cheerio = require('cheerio');
var client = require('redis').createClient(process.env.REDIS_URL);

// Set up app
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.cookieJar = request.jar(); // Global cookie store
app.cachedData = { "post": "" }; // Basic format for JSON response
app.loggedIn = false; // Indicates whether app has login cookie for ETI

// Set up routing for API
var routes = require("./routes/routes.js")(app);

// Set up server
var server = app.listen(process.env.PORT || LOCAL_HOST, () => {
	console.log(process.env.USERNAME + "-bot is sentient and ready to shitpost");
});

/**
	*		Redis db handler
	*/

client.on("connect", () => {
		// Attempt to get "quotes" array from Redis
		client.lrange("quotes", 0, -1, (error, items) => {
			if (error) {
				// TODO: Store emergency vesper.txt somewhere in repo and use that instead
				throw error;
			}
			else {
				input = items;
				app.generateMarkovChain();
			}
		});
});

/**
  * Markov chain methods
	*/

app.generateMarkovChain = function(shouldReturn) {
	var output = [];

	app.createArrays();

	// Pick random first word to start with
	currentWord = firstWords[Math.floor((Math.random() * firstWords.length))];

	for (let i = 0, len = app.getPostLength(); i < len; i++) {
		output.push(currentWord);

		var nextWord = app.generateNextWord();

		if (nextWord) {
			currentWord = nextWord;
		}
		else {
			currentWord = "";
		}
	}

	app.cachedData.post = output.join(" ").trim();

	if (shouldReturn) {
		return app.cachedData.post;
	}
};

app.getPostLength = function() {
	return input[Math.floor((Math.random() * input.length))].length;
};

app.createArrays = function() {
	// Create array containing first word of each sentence
	for (let i = 0, len = input.length; i < len; i++) {
		let sentence = input[i];
		let firstWord = sentence.split(" ")[0];
		firstWords.push(firstWord);
	}
};

app.generateNextWord = function() {
	var tempArray = [];
	// Iterate over input array to find instances of current word
	for (let i = 0, len = input.length - 1; i < len; i++) {
		let sentenceToCheck = input[i].split(" ");

		for (let j = 0, len = sentenceToCheck.length; j < len; j++) {
			let wordToCheck = sentenceToCheck[j];
			// create array of words that are likely to follow current word
			if (wordToCheck === currentWord) {
				// Make sure that item at index i + 1 exists
				if (j < len - 1) {
					let nextWord = sentenceToCheck[j + 1];
					tempArray.push(nextWord);
				}
				else {
					// reached end of array - do nothing
				}
			}
		}
	}

	let rnd = Math.floor((Math.random() * tempArray.length));

	if (tempArray[rnd]) {
		return tempArray[rnd];
	}

	else {
		return;
	}
};

/**
  *		Method which allows us to add new quotes from pastebin raw links
  */

app.addNewQuotes = function(url) {
	request.get(url, ((error, response, body) => {
		if (!error && response.statusCode == 200) {
			// Ignore blank lines,
			var quotes = body.split("\r\n").filter((line) => line && line.indexOf("<") == -1 && line.charAt(0) !== "<");

			client.lrange("quotes", 0, -1, (error, items) => {
				if (error) {
					throw error;
				}
				else {
					// Update local & disk cache
					input = input.concat(quotes);
					client.rpush.apply(client, ["quotes"].concat(quotes).concat(() => { console.log("Quotes stored to redis") }));
				}
			});

		}

	}));
};

/**
  *		Log into ETI using environment vars as credentials
  */

app.loginToBlueSite = function() {
	const LOGIN_URL = "https://endoftheinter.net/";
	const formData = { b: process.env.USERNAME, p: process.env.PASSWORD };

	request.post({

		headers: {"content-type": "application/x-www-form-urlencoded"},
		url: LOGIN_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			// After successful login, ETI will attempt to redirect you to homepage
			if (!error && response.statusCode === 302) {
					app.getTopicList();
			}

			else {
				console.log("Login failed.");
				throw error;
			}
	});
};

app.getTopicList = function() {
	var LUE_TOPICS = "https://boards.endoftheinter.net/topics/LUE";

	request({

		url: LUE_TOPICS,
		jar: app.cookieJar

	}
	, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			try {
				var $ = cheerio.load(body);
				// Pick random topic to pester
				var anchors = $('td.oh div.fl a').get();
				console.log("anchors length: " + anchors.length);

				var anchor = $('td.oh div.fl a').get(Math.floor(Math.random() * 49) + 1);
				console.log("anchor: " + anchor.attr('href'));

				var href = anchor.attr('href');

				app.getMessageList(href);

			} catch (e) {

				console.log("Error while parsing topic list:");
				throw e;
			}
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.getMessageList = function(url) {

	request({

		url: url,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			var $ = cheerio.load(body);
			// Can't make POST requests without the value of this token, scraped from quickpost area
			currentToken = $('input[name="h"]').value();
			app.contributeToDiscussion();
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.contributeToDiscussion = function() {
	const QUICKPOST_URL = "http://boards.endoftheinter.net/async-post.php";

	var formData = {};
			formData.topic = currentTopic;
			formData.h = currentToken;
			formData.message = app.generateMarkovChain(true); // Pass true to get return value
			formData.submit = "Post Message";

	request.post({

		url: QUICKPOST_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			if (!error && response.statusCode === 200) {
				console.log("Post successful");
			}

			else {
				console.log("Post unsuccessful: code ", response.statusCode);
				throw error;
			}

	});
};<|endoftext|>


<commit_before>"use strict";

(function($, TOKEN) {

  // do not change below this line
  var SERVICE_URL = "https://suggestions.dadata.ru/suggestions/api/4_1/rs";
  var PARTNER = "STORELAND";
  var AJAX_DELAY_MS = 2000;
  var QUICK_ORDER_BTN_ID = "#startOrder";

  // Storeland function, exposed as global
  var storelandQuickOrderFn = window.startOrder;

  var fields = {
    name: ["reg_name", "feedback_name", "callback_person"],
    email: ["contactEmail", "sites_client_mail", "feedback_email", "email"],
    address: ["deliveryAddress", "quickDeliveryAddress", "sites_client_addr"]
  };
  var deliveryAddressSelectors = "#quickDeliveryAddress, #deliveryAddress";
  var deliveryCitySelectors = "#quickDeliveryCity, #deliveryCity";
  var deliveryPostalSelectors = "#quickDeliveryZipCode, #deliveryZipCode";

  // initialize suggestions on all known fields
  function init() {
    initSuggestions();
    initDelivery();
  }

  function initSuggestions() {
    for (var type in fields) {
      fields[type].forEach(function (id) {
        $("#"+id).suggestions({
          serviceUrl: SERVICE_URL,
          token: TOKEN,
          partner: PARTNER,
          type: type.toUpperCase()
        });
      });
    }
  }

  function initDelivery() {
    var $city = $(deliveryCitySelectors);
    var $address = $(deliveryAddressSelectors);
    var $postalCode = $(deliveryPostalSelectors);
    suggestCity($city, $address, $postalCode);
    suggestPostalCode($address, $postalCode);
  }

  // constrain address by kladr id
  function setAddressConstraint(kladr_id, $address) {
    $address.suggestions().setOptions({
      constraints: {
        locations: { kladr_id: kladr_id }
      },
      restrict_value: true
    });
  }

  // clear address constraints
  function clearAddressConstraint($address) {
    $address.suggestions().setOptions({
      constraints: {},
      restrict_value: false
    });
  }

  // suggest city and restrict address accordingly
  // also fill postal code
  function suggestCity($city, $address, $postalCode) {
    if (!$city.length) {
      return;
    }
    $city.suggestions({
      serviceUrl: SERVICE_URL,
      token: TOKEN,
      partner: PARTNER,
      type: "ADDRESS",
      hint: false,
      bounds: "city-settlement",
      onSelect: function(suggestion) {
        setAddressConstraint(suggestion.data.kladr_id, $address);
        $address.suggestions().clear();
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        clearAddressConstraint($address);
        $address.suggestions().clear();
        $postalCode.val("");
      }
    });
  }

  // fill postal code when address is selected
  function suggestPostalCode($address, $postalCode) {
    if (!$postalCode.length || !$address.length) {
      return;
    }
    $address.suggestions().setOptions({
      onSelect: function(suggestion) {
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        $postalCode.val("");
      }
    });
  }

  $(function () {
    init();
  });

  // quick order form is loaded via AJAX after button click
  $(QUICK_ORDER_BTN_ID)
    .off()
    .on("click", function(e) {
      storelandQuickOrderFn();
      window.setTimeout(init, AJAX_DELAY_MS);
      return false;
    });

}(window.jQuery, window.DADATA_TOKEN))
<commit_msg>Support for quickDeliveryAddressStreet<commit_after>"use strict";

(function($, TOKEN) {

  // do not change below this line
  var SERVICE_URL = "https://suggestions.dadata.ru/suggestions/api/4_1/rs";
  var PARTNER = "STORELAND";
  var AJAX_DELAY_MS = 2000;
  var QUICK_ORDER_BTN_ID = "#startOrder";

  // Storeland function, exposed as global
  var storelandQuickOrderFn = window.startOrder;

  var fields = {
    name: ["reg_name", "feedback_name", "callback_person"],
    email: ["contactEmail", "sites_client_mail", "feedback_email", "email"],
    address: ["deliveryAddress", "quickDeliveryAddress", "sites_client_addr"]
  };
  var deliveryAddressSelectors = "#quickDeliveryAddress, #quickDeliveryAddressStreet, #deliveryAddress";
  var deliveryCitySelectors = "#quickDeliveryCity, #deliveryCity";
  var deliveryPostalSelectors = "#quickDeliveryZipCode, #deliveryZipCode";

  // initialize suggestions on all known fields
  function init() {
    initSuggestions();
    initDelivery();
  }

  function initSuggestions() {
    for (var type in fields) {
      fields[type].forEach(function (id) {
        $("#"+id).suggestions({
          serviceUrl: SERVICE_URL,
          token: TOKEN,
          partner: PARTNER,
          type: type.toUpperCase()
        });
      });
    }
  }

  function initDelivery() {
    var $city = $(deliveryCitySelectors);
    var $address = $(deliveryAddressSelectors);
    var $postalCode = $(deliveryPostalSelectors);
    suggestCity($city, $address, $postalCode);
    suggestPostalCode($address, $postalCode);
  }

  // constrain address by kladr id
  function setAddressConstraint(kladr_id, $address) {
    $address.suggestions().setOptions({
      constraints: {
        locations: { kladr_id: kladr_id }
      },
      restrict_value: true
    });
  }

  // clear address constraints
  function clearAddressConstraint($address) {
    $address.suggestions().setOptions({
      constraints: {},
      restrict_value: false
    });
  }

  // suggest city and restrict address accordingly
  // also fill postal code
  function suggestCity($city, $address, $postalCode) {
    if (!$city.length) {
      return;
    }
    $city.suggestions({
      serviceUrl: SERVICE_URL,
      token: TOKEN,
      partner: PARTNER,
      type: "ADDRESS",
      hint: false,
      bounds: "city-settlement",
      onSelect: function(suggestion) {
        setAddressConstraint(suggestion.data.kladr_id, $address);
        $address.suggestions().clear();
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        clearAddressConstraint($address);
        $address.suggestions().clear();
        $postalCode.val("");
      }
    });
  }

  // fill postal code when address is selected
  function suggestPostalCode($address, $postalCode) {
    if (!$postalCode.length || !$address.length) {
      return;
    }
    $address.suggestions().setOptions({
      onSelect: function(suggestion) {
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        $postalCode.val("");
      }
    });
  }

  $(function () {
    init();
  });

  // quick order form is loaded via AJAX after button click
  $(QUICK_ORDER_BTN_ID)
    .off()
    .on("click", function(e) {
      storelandQuickOrderFn();
      window.setTimeout(init, AJAX_DELAY_MS);
      return false;
    });

}(window.jQuery, window.DADATA_TOKEN))
<|endoftext|>


<commit_before>/*
    Copyright 2012 Rustici Software

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/**
TinCan client library

@module TinCan
@submodule TinCan.Utils
**/
(function () {
    "use strict";

    /**
    @class TinCan.Utils
    */
    TinCan.Utils = {
        /**
        @method getUUID
        @return {String} UUID
        @static

        Excerpt from: Math.uuid.js (v1.4)
        http://www.broofa.com
        mailto:robert@broofa.com
        Copyright (c) 2010 Robert Kieffer
        Dual licensed under the MIT and GPL licenses.
        */
        getUUID: function () {
            /*jslint bitwise: true eqeq: true */
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
                /[xy]/g,
                function (c) {
                    var r = Math.random() * 16|0, v = c == 'x' ? r : (r&0x3|0x8);
                    return v.toString(16);
                }
            );
        },

        /**
        @method getISODateString
        @static
        @param {Date} date Date to stringify
        @return {String} ISO date String
        */
        getISODateString: function (d) {
            function pad (val, n) {
                var padder,
                    tempVal;
                if (val === null) {
                    val = 0;
                }
                if (n === null) {
                    n = 2;
                }
                padder = Math.pow(10, n-1);
                tempVal = val.toString();

                while (val < padder && padder > 1) {
                    tempVal = '0' + tempVal;
                    padder = padder / 10;
                }

                return tempVal;
            }

            return d.getUTCFullYear() + '-'
                + pad(d.getUTCMonth() + 1) + '-'
                + pad(d.getUTCDate()) + 'T'
                + pad(d.getUTCHours()) + ':'
                + pad(d.getUTCMinutes()) + ':'
                + pad(d.getUTCSeconds()) + '.'
                + pad(d.getUTCMilliseconds(), 3) + 'Z';
        },

        /**
        @method getSHA1String
        @static
        @param {String} str Content to hash
        @return {String} SHA1 for contents
        */
        getSHA1String: function (str) {
            /*global CryptoJS*/

            return CryptoJS.SHA1(str).toString(CryptoJS.enc.Hex);
        },

        /**
        @method getLangDictionaryValue
        @param {String} prop Property name storing the dictionary
        @param {String} [lang] Language to return
        @return {String}

        Intended to be inherited by objects with properties that store
        display values in a language based "dictionary"
        */
        getLangDictionaryValue: function (prop, lang) {
            var langDict = this[prop],
                key;

            if (typeof lang !== "undefined" && typeof langDict[lang] !== "undefined") {
                return langDict[lang];
            }
            if (typeof langDict.und !== "undefined") {
                return langDict.und;
            }
            if (typeof langDict["en-US"] !== "undefined") {
                return langDict["en-US"];
            }
            for (key in langDict) {
                if (langDict.hasOwnProperty(key)) {
                    return langDict[key];
                }
            }

            return "";
        },

        /**
        @method parseURL
        @param {String} url
        @return {Object} Object of values
        @private
        */
        parseURL: function (url) {
            var parts = String(url).split('?'),
                pairs,
                pair,
                i,
                params = {}
            ;
            if (parts.length === 2) {
                pairs = parts[1].split('&');
                for (i = 0; i < pairs.length; i += 1) {
                    pair = pairs[i].split('=');
                    if (pair.length === 2 && pair[0]) {
                        params[pair[0]] = decodeURIComponent(pair[1]);
                    }
                }
            }

            return {
                path: parts[0],
                params: params
            };
        },

        /**
        @method getServerRoot
        @param {String} absoluteUrl
        @return {String} server root of url
        @private
        */
        getServerRoot: function (absoluteUrl) {
            var urlParts = absoluteUrl.split("/");
            return urlParts[0] + "//" + urlParts[2];
        }
    };
}());
<commit_msg>a fix for how we use the pad function. it was breaking the during the first 9 seconds of every minute in Firefox<commit_after>/*
    Copyright 2012 Rustici Software

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/**
TinCan client library

@module TinCan
@submodule TinCan.Utils
**/
(function () {
    "use strict";

    /**
    @class TinCan.Utils
    */
    TinCan.Utils = {
        /**
        @method getUUID
        @return {String} UUID
        @static

        Excerpt from: Math.uuid.js (v1.4)
        http://www.broofa.com
        mailto:robert@broofa.com
        Copyright (c) 2010 Robert Kieffer
        Dual licensed under the MIT and GPL licenses.
        */
        getUUID: function () {
            /*jslint bitwise: true eqeq: true */
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
                /[xy]/g,
                function (c) {
                    var r = Math.random() * 16|0, v = c == 'x' ? r : (r&0x3|0x8);
                    return v.toString(16);
                }
            );
        },

        /**
        @method getISODateString
        @static
        @param {Date} date Date to stringify
        @return {String} ISO date String
        */
        getISODateString: function (d) {
            function pad (val, n) {
                var padder,
                    tempVal;
                if (typeof val === "undefined" || val === null) {
                    val = 0;
                }
                if (typeof n === "undefined" || n === null) {
                    n = 2;
                }
                padder = Math.pow(10, n-1);
                tempVal = val.toString();

                while (val < padder && padder > 1) {
                    tempVal = '0' + tempVal;
                    padder = padder / 10;
                }

                return tempVal;
            }

            return d.getUTCFullYear() + '-'
                + pad(d.getUTCMonth() + 1) + '-'
                + pad(d.getUTCDate()) + 'T'
                + pad(d.getUTCHours()) + ':'
                + pad(d.getUTCMinutes()) + ':'
                + pad(d.getUTCSeconds()) + '.'
                + pad(d.getUTCMilliseconds(), 3) + 'Z';
        },

        /**
        @method getSHA1String
        @static
        @param {String} str Content to hash
        @return {String} SHA1 for contents
        */
        getSHA1String: function (str) {
            /*global CryptoJS*/

            return CryptoJS.SHA1(str).toString(CryptoJS.enc.Hex);
        },

        /**
        @method getLangDictionaryValue
        @param {String} prop Property name storing the dictionary
        @param {String} [lang] Language to return
        @return {String}

        Intended to be inherited by objects with properties that store
        display values in a language based "dictionary"
        */
        getLangDictionaryValue: function (prop, lang) {
            var langDict = this[prop],
                key;

            if (typeof lang !== "undefined" && typeof langDict[lang] !== "undefined") {
                return langDict[lang];
            }
            if (typeof langDict.und !== "undefined") {
                return langDict.und;
            }
            if (typeof langDict["en-US"] !== "undefined") {
                return langDict["en-US"];
            }
            for (key in langDict) {
                if (langDict.hasOwnProperty(key)) {
                    return langDict[key];
                }
            }

            return "";
        },

        /**
        @method parseURL
        @param {String} url
        @return {Object} Object of values
        @private
        */
        parseURL: function (url) {
            var parts = String(url).split('?'),
                pairs,
                pair,
                i,
                params = {}
            ;
            if (parts.length === 2) {
                pairs = parts[1].split('&');
                for (i = 0; i < pairs.length; i += 1) {
                    pair = pairs[i].split('=');
                    if (pair.length === 2 && pair[0]) {
                        params[pair[0]] = decodeURIComponent(pair[1]);
                    }
                }
            }

            return {
                path: parts[0],
                params: params
            };
        },

        /**
        @method getServerRoot
        @param {String} absoluteUrl
        @return {String} server root of url
        @private
        */
        getServerRoot: function (absoluteUrl) {
            var urlParts = absoluteUrl.split("/");
            return urlParts[0] + "//" + urlParts[2];
        }
    };
}());
<|endoftext|>


<commit_before>import Component from '@ember/component';
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
import {action} from '@ember/object';
import {computed} from '@ember/object';
import {or, reads} from '@ember/object/computed';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';

const CONFIRM_EMAIL_POLL_LENGTH = 1000;
const CONFIRM_EMAIL_MAX_POLL_LENGTH = 15 * 1000;

export default Component.extend({
    clock: service(),
    feature: service(),
    settings: service(),
    config: service(),
    session: service(),
    store: service(),
    limit: service(),

    classNames: 'gh-publishmenu',
    displayState: 'draft',
    post: null,
    postStatus: 'draft',
    runningText: null,
    saveTask: null,
    sendEmailWhenPublished: null,
    typedDateError: null,
    isSendingEmailLimited: false,
    sendingEmailLimitError: '',

    _publishedAtBlogTZ: null,
    _previousStatus: null,

    isClosing: null,

    onClose() {},

    forcePublishedMenu: reads('post.pastScheduledTime'),

    hasEmailPermission: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),

    canSendEmail: computed('hasEmailPermission', 'post.{isPost,email}', 'settings.{editorDefaultEmailRecipients,membersSignupAccess,mailgunIsConfigured}', 'config.mailgunIsConfigured', function () {
        let isDisabled = this.settings.get('editorDefaultEmailRecipients') === 'disabled' || this.settings.get('membersSignupAccess') === 'none';
        let mailgunIsConfigured = this.settings.get('mailgunIsConfigured') || this.config.get('mailgunIsConfigured');
        let isPost = this.post.isPost;
        let hasSentEmail = !!this.post.email;

        return this.hasEmailPermission &&
            !isDisabled &&
            mailgunIsConfigured &&
            isPost &&
            !hasSentEmail;
    }),

    postState: computed('post.{isPublished,isScheduled}', 'forcePublishedMenu', function () {
        if (this.forcePublishedMenu || this.get('post.isPublished')) {
            return 'published';
        } else if (this.get('post.isScheduled')) {
            return 'scheduled';
        } else {
            return 'draft';
        }
    }),

    triggerText: computed('postState', function () {
        let state = this.postState;

        if (state === 'published') {
            return 'Update';
        } else if (state === 'scheduled') {
            return 'Scheduled';
        } else {
            return 'Publish';
        }
    }),

    _runningText: computed('postState', 'saveType', function () {
        let saveType = this.saveType;
        let postState = this.postState;
        let runningText;

        if (postState === 'draft') {
            runningText = saveType === 'publish' ? 'Publishing' : 'Scheduling';
        }

        if (postState === 'published') {
            runningText = saveType === 'publish' ? 'Updating' : 'Unpublishing';
        }

        if (postState === 'scheduled') {
            runningText = saveType === 'schedule' ? 'Rescheduling' : 'Unscheduling';
        }

        return runningText || 'Publishing';
    }),

    buttonText: computed('postState', 'saveType', function () {
        let saveType = this.saveType;
        let postState = this.postState;
        let buttonText;

        if (postState === 'draft') {
            buttonText = saveType === 'publish' ? 'Publish' : 'Schedule';
        }

        if (postState === 'published') {
            buttonText = saveType === 'publish' ? 'Update' : 'Unpublish';
        }

        if (postState === 'scheduled') {
            buttonText = saveType === 'schedule' ? 'Reschedule' : 'Unschedule';
        }

        return buttonText || 'Publish';
    }),

    successText: computed('_previousStatus', 'postState', function () {
        let postState = this.postState;
        let previousStatus = this._previousStatus;
        let buttonText;

        if (previousStatus === 'draft') {
            buttonText = postState === 'published' ? 'Published' : 'Scheduled';
        }

        if (previousStatus === 'published') {
            buttonText = postState === 'draft' ? 'Unpublished' : 'Updated';
        }

        if (previousStatus === 'scheduled') {
            buttonText = postState === 'draft' ? 'Unscheduled' : 'Rescheduled';
        }

        return buttonText;
    }),

    defaultEmailRecipients: computed('settings.{editorDefaultEmailRecipients,editorDefaultEmailRecipientsFilter}', 'post.visibility', function () {
        const defaultEmailRecipients = this.settings.get('editorDefaultEmailRecipients');

        if (defaultEmailRecipients === 'disabled') {
            return null;
        }

        if (defaultEmailRecipients === 'visibility') {
            if (this.post.visibility === 'public' || this.post.visibility === 'members') {
                return 'status:free,status:-free';
            }

            if (this.post.visibility === 'paid') {
                return 'status:-free';
            }
        }

        return this.settings.get('editorDefaultEmailRecipientsFilter');
    }),

    didReceiveAttrs() {
        this._super(...arguments);

        // update the displayState based on the post status but only after a
        // save has finished to avoid swapping the menu prematurely and triggering
        // calls to `setSaveType` due to the component re-rendering
        // TODO: we should have a better way of dealing with this where we don't
        // rely on the side-effect of component rendering calling setSaveType
        let postStatus = this.postStatus;
        if (postStatus !== this._postStatus) {
            if (this.get('saveTask.isRunning')) {
                this.get('saveTask.last').then(() => {
                    this.set('displayState', postStatus);
                });
            } else {
                this.set('displayState', postStatus);
            }
        }

        this._postStatus = this.postStatus;
        this.setDefaultSendEmailWhenPublished();
        this.checkIsSendingEmailLimited();
    },

    actions: {
        setSaveType(saveType) {
            let post = this.post;

            this.set('saveType', saveType);

            if (saveType === 'draft') {
                post.set('statusScratch', 'draft');
            } else if (saveType === 'schedule') {
                post.set('statusScratch', 'scheduled');
            } else if (saveType === 'publish') {
                post.set('statusScratch', 'published');
            }
        },

        setSendEmailWhenPublished(sendEmailWhenPublished) {
            this.set('sendEmailWhenPublished', sendEmailWhenPublished);
        },

        open() {
            this._cachePublishedAtBlogTZ();
            this.set('isClosing', false);
            this.get('post.errors').clear();

            this.setDefaultSendEmailWhenPublished();

            if (this.onOpen) {
                this.onOpen();
            }
        },

        close(dropdown, e) {
            // don't close the menu if the datepicker popup or confirm modal is clicked
            if (e) {
                let onDatepicker = !!e.target.closest('.ember-power-datepicker-content');
                let onModal = !!e.target.closest('.fullscreen-modal-container');

                if (onDatepicker || onModal) {
                    return false;
                }
            }

            if (!this._skipDropdownCloseCleanup) {
                this._cleanup();
            }
            this._skipDropdownCloseCleanup = false;

            this.onClose();
            this.set('isClosing', true);

            return true;
        },

        updateMemberCount(count) {
            this.memberCount = count;
        }
    },

    setDefaultSendEmailWhenPublished() {
        if (this.postStatus === 'draft' && this.canSendEmail) {
            // Set default newsletter recipients
            this.set('sendEmailWhenPublished', this.defaultEmailRecipients);
        } else {
            this.set('sendEmailWhenPublished', this.post.emailRecipientFilter);
        }
    },

    checkIsSendingEmailLimited: action(function () {
        if (this.limit.limiter && this.limit.limiter.isLimited('emails')) {
            this.checkIsSendingEmailLimitedTask.perform();
        } else {
            this.set('isSendingEmailLimited', false);
            this.set('sendingEmailLimitError', null);
        }
    }),

    checkIsSendingEmailLimitedTask: task(function* () {
        try {
            yield this.limit.limiter.errorIfWouldGoOverLimit('emails');

            this.set('isSendingEmailLimited', false);
            this.set('sendingEmailLimitError', null);
        } catch (error) {
            this.set('isSendingEmailLimited', true);
            this.set('sendingEmailLimitError', error.message);
            this.set('sendEmailWhenPublished', 'none');
        }
    }),

    // action is required because <GhFullscreenModal> only uses actions
    confirmEmailSend: action(function () {
        return this._confirmEmailSend.perform();
    }),

    _confirmEmailSend: task(function* () {
        this.sendEmailConfirmed = true;
        let post = yield this.save.perform();

        // simulate a validation error if saving failed so that the confirm
        // modal can react accordingly
        if (!post || post.errors.length) {
            throw null;
        }

        let pollTimeout = 0;
        if (post.email && post.email.status !== 'submitted') {
            while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {
                yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
                post = yield post.reload();

                if (post.email.status === 'submitted') {
                    break;
                }
                if (post.email.status === 'failed') {
                    throw new EmailFailedError(post.email.error);
                }
            }
        }

        return post;
    }),

    retryEmailSend: action(function () {
        return this._retryEmailSend.perform();
    }),

    _retryEmailSend: task(function* () {
        if (!this.post.email) {
            return;
        }

        let email = yield this.post.email.retry();

        let pollTimeout = 0;
        if (email && email.status !== 'submitted') {
            while (pollTimeout < CONFIRM_EMAIL_POLL_LENGTH) {
                yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
                email = yield email.reload();

                if (email.status === 'submitted') {
                    break;
                }
                if (email.status === 'failed') {
                    throw new EmailFailedError(email.error);
                }
            }
        }

        return email;
    }),

    openEmailConfirmationModal: action(function (dropdown) {
        if (dropdown) {
            this._skipDropdownCloseCleanup = true;
            dropdown.actions.close();
        }
        this.set('showEmailConfirmationModal', true);
    }),

    closeEmailConfirmationModal: action(function () {
        this.set('showEmailConfirmationModal', false);
        this._cleanup();
    }),

    save: task(function* ({dropdown} = {}) {
        let {
            post,
            sendEmailWhenPublished,
            sendEmailConfirmed,
            saveType,
            typedDateError
        } = this;

        // don't allow save if an invalid schedule date is present
        if (typedDateError) {
            return false;
        }

        // validate publishedAtBlog to avoid an alert when saving for already displayed errors
        // important to do this before opening email confirmation modal too
        try {
            yield post.validate({property: 'publishedAtBlog'});
        } catch (error) {
            // re-throw if we don't have a validation error
            if (error) {
                throw error;
            }
            return false;
        }

        if (
            post.status === 'draft' &&
            !post.email && // email sent previously
            sendEmailWhenPublished && sendEmailWhenPublished !== 'none' &&
            !sendEmailConfirmed // set once confirmed so normal save happens
        ) {
            this.openEmailConfirmationModal(dropdown);
            return;
        }

        this.sendEmailConfirmed = false;

        // runningText needs to be declared before the other states change during the
        // save action.
        this.set('runningText', this._runningText);
        this.set('_previousStatus', this.get('post.status'));
        this.setSaveType(saveType);

        try {
            // will show alert for non-date related failed validations
            post = yield this.saveTask.perform({sendEmailWhenPublished});

            this._cachePublishedAtBlogTZ();
            return post;
        } catch (error) {
            // re-throw if we don't have a validation error
            if (error) {
                throw error;
            }
        }
    }),

    _cachePublishedAtBlogTZ() {
        this._publishedAtBlogTZ = this.get('post.publishedAtBlogTZ');
    },

    _cleanup() {
        this.set('showConfirmEmailModal', false);

        // when closing the menu we reset the publishedAtBlogTZ date so that the
        // unsaved changes made to the scheduled date aren't reflected in the PSM
        this.post.set('publishedAtBlogTZ', this._publishedAtBlogTZ);

        this.post.set('statusScratch', null);
        this.post.validate();
    }
});
<commit_msg>Fixed publish menu email recipients not matching post visibility<commit_after>import Component from '@ember/component';
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
import {action} from '@ember/object';
import {computed} from '@ember/object';
import {or, reads} from '@ember/object/computed';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';

const CONFIRM_EMAIL_POLL_LENGTH = 1000;
const CONFIRM_EMAIL_MAX_POLL_LENGTH = 15 * 1000;

export default Component.extend({
    clock: service(),
    feature: service(),
    settings: service(),
    config: service(),
    session: service(),
    store: service(),
    limit: service(),

    classNames: 'gh-publishmenu',
    displayState: 'draft',
    post: null,
    postStatus: 'draft',
    runningText: null,
    saveTask: null,
    sendEmailWhenPublished: null,
    typedDateError: null,
    isSendingEmailLimited: false,
    sendingEmailLimitError: '',

    _publishedAtBlogTZ: null,
    _previousStatus: null,

    isClosing: null,

    onClose() {},

    forcePublishedMenu: reads('post.pastScheduledTime'),

    hasEmailPermission: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),

    canSendEmail: computed('hasEmailPermission', 'post.{isPost,email}', 'settings.{editorDefaultEmailRecipients,membersSignupAccess,mailgunIsConfigured}', 'config.mailgunIsConfigured', function () {
        let isDisabled = this.settings.get('editorDefaultEmailRecipients') === 'disabled' || this.settings.get('membersSignupAccess') === 'none';
        let mailgunIsConfigured = this.settings.get('mailgunIsConfigured') || this.config.get('mailgunIsConfigured');
        let isPost = this.post.isPost;
        let hasSentEmail = !!this.post.email;

        return this.hasEmailPermission &&
            !isDisabled &&
            mailgunIsConfigured &&
            isPost &&
            !hasSentEmail;
    }),

    postState: computed('post.{isPublished,isScheduled}', 'forcePublishedMenu', function () {
        if (this.forcePublishedMenu || this.get('post.isPublished')) {
            return 'published';
        } else if (this.get('post.isScheduled')) {
            return 'scheduled';
        } else {
            return 'draft';
        }
    }),

    triggerText: computed('postState', function () {
        let state = this.postState;

        if (state === 'published') {
            return 'Update';
        } else if (state === 'scheduled') {
            return 'Scheduled';
        } else {
            return 'Publish';
        }
    }),

    _runningText: computed('postState', 'saveType', function () {
        let saveType = this.saveType;
        let postState = this.postState;
        let runningText;

        if (postState === 'draft') {
            runningText = saveType === 'publish' ? 'Publishing' : 'Scheduling';
        }

        if (postState === 'published') {
            runningText = saveType === 'publish' ? 'Updating' : 'Unpublishing';
        }

        if (postState === 'scheduled') {
            runningText = saveType === 'schedule' ? 'Rescheduling' : 'Unscheduling';
        }

        return runningText || 'Publishing';
    }),

    buttonText: computed('postState', 'saveType', function () {
        let saveType = this.saveType;
        let postState = this.postState;
        let buttonText;

        if (postState === 'draft') {
            buttonText = saveType === 'publish' ? 'Publish' : 'Schedule';
        }

        if (postState === 'published') {
            buttonText = saveType === 'publish' ? 'Update' : 'Unpublish';
        }

        if (postState === 'scheduled') {
            buttonText = saveType === 'schedule' ? 'Reschedule' : 'Unschedule';
        }

        return buttonText || 'Publish';
    }),

    successText: computed('_previousStatus', 'postState', function () {
        let postState = this.postState;
        let previousStatus = this._previousStatus;
        let buttonText;

        if (previousStatus === 'draft') {
            buttonText = postState === 'published' ? 'Published' : 'Scheduled';
        }

        if (previousStatus === 'published') {
            buttonText = postState === 'draft' ? 'Unpublished' : 'Updated';
        }

        if (previousStatus === 'scheduled') {
            buttonText = postState === 'draft' ? 'Unscheduled' : 'Rescheduled';
        }

        return buttonText;
    }),

    defaultEmailRecipients: computed('settings.{editorDefaultEmailRecipients,editorDefaultEmailRecipientsFilter}', 'post.visibility', function () {
        const defaultEmailRecipients = this.settings.get('editorDefaultEmailRecipients');

        if (defaultEmailRecipients === 'disabled') {
            return null;
        }

        if (defaultEmailRecipients === 'visibility') {
            if (this.post.visibility === 'public') {
                return 'status:free,status:-free';
            }

            return this.post.visibility;
        }

        return this.settings.get('editorDefaultEmailRecipientsFilter');
    }),

    didReceiveAttrs() {
        this._super(...arguments);

        // update the displayState based on the post status but only after a
        // save has finished to avoid swapping the menu prematurely and triggering
        // calls to `setSaveType` due to the component re-rendering
        // TODO: we should have a better way of dealing with this where we don't
        // rely on the side-effect of component rendering calling setSaveType
        let postStatus = this.postStatus;
        if (postStatus !== this._postStatus) {
            if (this.get('saveTask.isRunning')) {
                this.get('saveTask.last').then(() => {
                    this.set('displayState', postStatus);
                });
            } else {
                this.set('displayState', postStatus);
            }
        }

        this._postStatus = this.postStatus;
        this.setDefaultSendEmailWhenPublished();
        this.checkIsSendingEmailLimited();
    },

    actions: {
        setSaveType(saveType) {
            let post = this.post;

            this.set('saveType', saveType);

            if (saveType === 'draft') {
                post.set('statusScratch', 'draft');
            } else if (saveType === 'schedule') {
                post.set('statusScratch', 'scheduled');
            } else if (saveType === 'publish') {
                post.set('statusScratch', 'published');
            }
        },

        setSendEmailWhenPublished(sendEmailWhenPublished) {
            this.set('sendEmailWhenPublished', sendEmailWhenPublished);
        },

        open() {
            this._cachePublishedAtBlogTZ();
            this.set('isClosing', false);
            this.get('post.errors').clear();

            this.setDefaultSendEmailWhenPublished();

            if (this.onOpen) {
                this.onOpen();
            }
        },

        close(dropdown, e) {
            // don't close the menu if the datepicker popup or confirm modal is clicked
            if (e) {
                let onDatepicker = !!e.target.closest('.ember-power-datepicker-content');
                let onModal = !!e.target.closest('.fullscreen-modal-container');

                if (onDatepicker || onModal) {
                    return false;
                }
            }

            if (!this._skipDropdownCloseCleanup) {
                this._cleanup();
            }
            this._skipDropdownCloseCleanup = false;

            this.onClose();
            this.set('isClosing', true);

            return true;
        },

        updateMemberCount(count) {
            this.memberCount = count;
        }
    },

    setDefaultSendEmailWhenPublished() {
        if (this.postStatus === 'draft' && this.canSendEmail) {
            // Set default newsletter recipients
            this.set('sendEmailWhenPublished', this.defaultEmailRecipients);
        } else {
            this.set('sendEmailWhenPublished', this.post.emailRecipientFilter);
        }
    },

    checkIsSendingEmailLimited: action(function () {
        if (this.limit.limiter && this.limit.limiter.isLimited('emails')) {
            this.checkIsSendingEmailLimitedTask.perform();
        } else {
            this.set('isSendingEmailLimited', false);
            this.set('sendingEmailLimitError', null);
        }
    }),

    checkIsSendingEmailLimitedTask: task(function* () {
        try {
            yield this.limit.limiter.errorIfWouldGoOverLimit('emails');

            this.set('isSendingEmailLimited', false);
            this.set('sendingEmailLimitError', null);
        } catch (error) {
            this.set('isSendingEmailLimited', true);
            this.set('sendingEmailLimitError', error.message);
            this.set('sendEmailWhenPublished', 'none');
        }
    }),

    // action is required because <GhFullscreenModal> only uses actions
    confirmEmailSend: action(function () {
        return this._confirmEmailSend.perform();
    }),

    _confirmEmailSend: task(function* () {
        this.sendEmailConfirmed = true;
        let post = yield this.save.perform();

        // simulate a validation error if saving failed so that the confirm
        // modal can react accordingly
        if (!post || post.errors.length) {
            throw null;
        }

        let pollTimeout = 0;
        if (post.email && post.email.status !== 'submitted') {
            while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {
                yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
                post = yield post.reload();

                if (post.email.status === 'submitted') {
                    break;
                }
                if (post.email.status === 'failed') {
                    throw new EmailFailedError(post.email.error);
                }
            }
        }

        return post;
    }),

    retryEmailSend: action(function () {
        return this._retryEmailSend.perform();
    }),

    _retryEmailSend: task(function* () {
        if (!this.post.email) {
            return;
        }

        let email = yield this.post.email.retry();

        let pollTimeout = 0;
        if (email && email.status !== 'submitted') {
            while (pollTimeout < CONFIRM_EMAIL_POLL_LENGTH) {
                yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
                email = yield email.reload();

                if (email.status === 'submitted') {
                    break;
                }
                if (email.status === 'failed') {
                    throw new EmailFailedError(email.error);
                }
            }
        }

        return email;
    }),

    openEmailConfirmationModal: action(function (dropdown) {
        if (dropdown) {
            this._skipDropdownCloseCleanup = true;
            dropdown.actions.close();
        }
        this.set('showEmailConfirmationModal', true);
    }),

    closeEmailConfirmationModal: action(function () {
        this.set('showEmailConfirmationModal', false);
        this._cleanup();
    }),

    save: task(function* ({dropdown} = {}) {
        let {
            post,
            sendEmailWhenPublished,
            sendEmailConfirmed,
            saveType,
            typedDateError
        } = this;

        // don't allow save if an invalid schedule date is present
        if (typedDateError) {
            return false;
        }

        // validate publishedAtBlog to avoid an alert when saving for already displayed errors
        // important to do this before opening email confirmation modal too
        try {
            yield post.validate({property: 'publishedAtBlog'});
        } catch (error) {
            // re-throw if we don't have a validation error
            if (error) {
                throw error;
            }
            return false;
        }

        if (
            post.status === 'draft' &&
            !post.email && // email sent previously
            sendEmailWhenPublished && sendEmailWhenPublished !== 'none' &&
            !sendEmailConfirmed // set once confirmed so normal save happens
        ) {
            this.openEmailConfirmationModal(dropdown);
            return;
        }

        this.sendEmailConfirmed = false;

        // runningText needs to be declared before the other states change during the
        // save action.
        this.set('runningText', this._runningText);
        this.set('_previousStatus', this.get('post.status'));
        this.setSaveType(saveType);

        try {
            // will show alert for non-date related failed validations
            post = yield this.saveTask.perform({sendEmailWhenPublished});

            this._cachePublishedAtBlogTZ();
            return post;
        } catch (error) {
            // re-throw if we don't have a validation error
            if (error) {
                throw error;
            }
        }
    }),

    _cachePublishedAtBlogTZ() {
        this._publishedAtBlogTZ = this.get('post.publishedAtBlogTZ');
    },

    _cleanup() {
        this.set('showConfirmEmailModal', false);

        // when closing the menu we reset the publishedAtBlogTZ date so that the
        // unsaved changes made to the scheduled date aren't reflected in the PSM
        this.post.set('publishedAtBlogTZ', this._publishedAtBlogTZ);

        this.post.set('statusScratch', null);
        this.post.validate();
    }
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function(global) {

var AbRecipientImagePopupSidebar = {
  get tree() {
    return document.getElementById('abResultsTree');
  },

  init: function() {
    window.removeEventListener('load', this, false);
    window.addEventListener('unload', this, false);
    this.tree.addEventListener('select', this, true);
    this.tree.addEventListener('mousemove', this, true);
  },

  destroy: function() {
    window.removeEventListener('unload', this, false);
    this.tree.removeEventListener('select', this, true);
    this.tree.removeEventListener('mousemove', this, true);
  },

  handleSelectionChange: function() {
    var cards = GetSelectedAbCards();
    if (cards.length > 0)
      this.showForCard(cards[0]);
  },

  handleMouseMove: function(aEvent) {
    if (this.delayedHandleMouseMoveTimer)
      window.clearTimeout(this.delayedHandleMouseMoveTimer);

    var row = gDirTree.treeBoxObject.getRowAt(aEvent.clientX, aEvent.clientY);
    var card = gAbView.getCardFromRow(row);

    this.delayedHandleMouseMoveTimer = window.setTimeout((function() {
      this.delayedHandleMouseMoveTimer = null;
      var maybeShown = card && this.showForCard(card);
      if (!maybeShown)
        AbRecipientImagePopup.hide();
    }).bind(this), 100);
  },

  showForCard: function(aCard) {
    AbRecipientImagePopup.hide();

    var directoryURI = GetSelectedDirectory();
    var directory =  GetDirectoryFromURI(directoryURI);

    AbRecipientImagePopup.show({
      card:          aCard,
      book:          directory,
      // Popup always eats click events on the anchor element, so
      // we have to specify other element as the anchor.
      // (The previous element of the tree is <separator/> and
      // events on the element can be ignored.)
      anchorElement: this.tree.previousSibling,
      position:      'side'
    });
  },

  handleEvent: function(aEvent) {
    switch (aEvent.type) {
      case 'load':
        this.init();
        return;

      case 'unload':
        this.destroy();
        return;

      case 'select':
        this.handleSelectionChange();
        return;

      case 'mousemove':
        this.handleMouseMove(aEvent);
        return;
    }
  }
};

window.addEventListener('load', AbRecipientImagePopupSidebar, false);

global.AbRecipientImagePopupSidebar = AbRecipientImagePopupSidebar;
})(this);
<commit_msg>Find card from mouse event on a tree correctly<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function(global) {

var AbRecipientImagePopupSidebar = {
  get tree() {
    return document.getElementById('abResultsTree');
  },

  init: function() {
    window.removeEventListener('load', this, false);
    window.addEventListener('unload', this, false);
    this.tree.addEventListener('select', this, true);
    this.tree.addEventListener('mousemove', this, true);
  },

  destroy: function() {
    window.removeEventListener('unload', this, false);
    this.tree.removeEventListener('select', this, true);
    this.tree.removeEventListener('mousemove', this, true);
  },

  handleSelectionChange: function() {
    var cards = GetSelectedAbCards();
    if (cards.length > 0)
      this.showForCard(cards[0]);
  },

  handleMouseMove: function(aEvent) {
    if (this.delayedHandleMouseMoveTimer)
      window.clearTimeout(this.delayedHandleMouseMoveTimer);

    var row = this.tree.treeBoxObject.getRowAt(aEvent.clientX, aEvent.clientY);
    var card = null;
    if (row) {
      card = gAbView.getCardFromRow(row);
    }

    this.delayedHandleMouseMoveTimer = window.setTimeout((function() {
      this.delayedHandleMouseMoveTimer = null;
      var maybeShown = card && this.showForCard(card);
      if (!maybeShown)
        AbRecipientImagePopup.hide();
    }).bind(this), 100);
  },

  showForCard: function(aCard) {
    AbRecipientImagePopup.hide();

    var directoryURI = GetSelectedDirectory();
    var directory =  GetDirectoryFromURI(directoryURI);

    AbRecipientImagePopup.show({
      card:          aCard,
      book:          directory,
      // Popup always eats click events on the anchor element, so
      // we have to specify other element as the anchor.
      // (The previous element of the tree is <separator/> and
      // events on the element can be ignored.)
      anchorElement: this.tree.previousSibling,
      position:      'side'
    });
  },

  handleEvent: function(aEvent) {
    switch (aEvent.type) {
      case 'load':
        this.init();
        return;

      case 'unload':
        this.destroy();
        return;

      case 'select':
        this.handleSelectionChange();
        return;

      case 'mousemove':
        this.handleMouseMove(aEvent);
        return;
    }
  }
};

window.addEventListener('load', AbRecipientImagePopupSidebar, false);

global.AbRecipientImagePopupSidebar = AbRecipientImagePopupSidebar;
})(this);
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Mycobacterium_Fortuitum
// @namespace   Mycobacterium_Fortuitum
// @include     http://agar.io/*
// @version     0.1
// @grant       none
// @author      https://github.com/filipdanic
// ==/UserScript==
var mycobacteriumFortuitumBotVersion = "dev_0.1";
const STANDARD_RATIO = 1.33;

window.botList = window.botList || [];

function MFortuitum() {
    this.name = "Mycobacterium_Fortuitum_" + mycobacteriumFortuitumBotVersion;
    this.keyAction = function(key) {w};
    this.displayText = function() {
        return [s];
    };

    /*
      <START: MATH UTILITIES>
    */

    /*
      To be bigger than the other blob we need to fit within the STANDARD_RATIO
      if enemy.size^2 * ratio is larger than player.size^2 then we need to be careful
    */
    this.compareSize = function(blob1, blob2, ratio) {
      if (blob1.size * blob1.size * ratio < blobl2.size * blob2.size) {
          return true;
      }
      return false;
    };
    this.computeDistanceBetweenBlobs = function(x1, y1, x2, y2, size1, size2) {
        /*
          size1 and size2 are optional arguments;
          replace them with 0 if they are not set
        */
        size1 = s1 || 0;
        size2 = s2 || 0;
        var x_distance = x1 - x2;
        var y_distance = y1 - y2;
        var distance = Math.sqrt(x_distance * x_distance + y_distance  * y_distance ) - (size1 + size2);

        return distance;
    };

    /*
      </END: MATH UTILITIES>
    */

    /*
      <START: GAME RECEPTORS>
    */

    /*
      Returns the mass of the blob based on it's size;
      from the wiki: mass = size*size / 100
    */
    this.getBlobMass = function(size) {
        return Math.pow(size / 10, 2);
    };
    /*
      is the observed cell a virus?
      the color of the virus is #33ff33
    */
    this.isVirus = function(blob, cell) {
      if (blob == null) {
          if (cell.isVirus()){return true;}
          else {return false;}
      }

      if (cell.isVirus() && this.compareSize(cell, blob, 1.2)) {
          return true;
      } else if (cell.isVirus() && cell.color.substring(3,5).toLowerCase() != "ff") {
          return true;
      }
      return false;
    };
    /*
      Check if a cell is considered food or not
      Anything that's below the 1.33 ratio or has a total size <= 13 is food!
    */
    this.isFood = function(blob, cell) {
        if (!cell.isVirus() && this.compareSize(cell, blob, STANDARD_RATIO) || (cell.size <= 13)) {
            return true;
        }
        return false;
    };
    /*
      Is the cell we're looking at a threat?
      If it's a bit bigger than us then it sure as hell is
    */
    this.isThreat = function(blob, cell) {

        if (!cell.isVirus() && this.compareSize(blob, cell, STANDARD_RATIO)) {
            return true;
        }
        return false;
    };

    /*
      </END: GAME RECEPTORS>
    */

    /*
      <START: MAIN BOT LOGIC>
    */

    /*
      Check if the cell is the player
      We don't want to add ourselves as potential food for example :)
    */
    this.isItMe = function(player, cell){
      for (var i = 0; i < player.length; i++) {
          if (cell.id == player[i].id) {
              return true;
          }
      }
      return false;
    }

    this.getAllObjects = function(that, listToUse, blob) {
        var foodElementList = [];
        var player = getPlayer();

        Object.keys(listToUse).forEach(function(element, index) {
            var identityCheck = that.isItMe(player, listToUse[element]);
            if (!identityCheck {
                if (that.isFood(blob, listToUse[element]) && listToUse[element].isNotMoving()) {
                    foodElementList.push(listToUse[element]);
                }
            }
        });

        foodList = [];
        for (var i = 0; i < foodElementList.length; i++) {
            foodList.push([foodElementList[i].x, foodElementList[i].y, foodElementList[i].size]);
        }

        return [foodList];
    };

    this.getMasterRecord = function(blob) {
        var allDotsList = [];
        var player = getPlayer();
        var interNodes = getMemoryCells();

        allDotsList = this.getAllObjects(this, interNodes, blob);
        return dotList;
    };

    /*
      </END: MAIN BOT LOGIC>
    */

    /*
      The Main Loop
    */
    this.mainLoop = function() {
      var player = getPlayer();
      var interNodes = getMemoryCells();
      var tempMoveX = getPointX();
      var tempMoveY = getPointY();
      var botMoveChoice = [];

        if (player.length > 0) {
          for (var k = 0; k < player.length; k++) {
              if (true) {
                  drawPoint(player[k].x, player[k].y + player[k].size, 0, "" + (getLastUpdate() - player[k].birth) + " / " + (30000 + (player[k].birthMass * 57) - (getLastUpdate() - player[k].birth)) + " / " + player[k].birthMass);
              }
          }
          for (var k = 0; /*k < player.length*/ k < 1; k++) {
            var allObjects = this.getAllMasterRecord(player[k]);
            var allPossibleFood = allObjects[0];
            console.log(allPossibleFood);
          }
        }
      var temp1 = floor(Math.random()*100);
      var temp2 = floor(Math.random()*100);
      botMoveChoice = [temp1, temp2];
      console.log(botMoveChoice);
      return botMoveChoice;

    };
}

MFortuitumBot = new MFortuitum();
window.botList.push(MFortuitumBot);
console.log("Pushed MFortuitum");
window.updateBotList(); //This function might not exist yet.
<commit_msg>Bug fix<commit_after>// ==UserScript==
// @name        Mycobacterium_Fortuitum
// @namespace   Mycobacterium_Fortuitum
// @include     http://agar.io/*
// @version     0.1
// @grant       none
// @author      https://github.com/filipdanic
// ==/UserScript==
var mycobacteriumFortuitumBotVersion = "dev_0.1";
const STANDARD_RATIO = 1.33;

window.botList = window.botList || [];

function MFortuitum() {
    this.name = "Mycobacterium_Fortuitum_" + mycobacteriumFortuitumBotVersion;
    this.keyAction = function(key) {w};
    this.displayText = function() {
        return [s];
    };

    /*
      <START: MATH UTILITIES>
    */

    /*
      To be bigger than the other blob we need to fit within the STANDARD_RATIO
      if enemy.size^2 * ratio is larger than player.size^2 then we need to be careful
    */
    this.compareSize = function(blob1, blob2, ratio) {
      if (blob1.size * blob1.size * ratio < blobl2.size * blob2.size) {
          return true;
      }
      return false;
    };
    this.computeDistanceBetweenBlobs = function(x1, y1, x2, y2, size1, size2) {
        /*
          size1 and size2 are optional arguments;
          replace them with 0 if they are not set
        */
        size1 = s1 || 0;
        size2 = s2 || 0;
        var x_distance = x1 - x2;
        var y_distance = y1 - y2;
        var distance = Math.sqrt(x_distance * x_distance + y_distance  * y_distance ) - (size1 + size2);

        return distance;
    };

    /*
      </END: MATH UTILITIES>
    */

    /*
      <START: GAME RECEPTORS>
    */

    /*
      Returns the mass of the blob based on it's size;
      from the wiki: mass = size*size / 100
    */
    this.getBlobMass = function(size) {
        return Math.pow(size / 10, 2);
    };
    /*
      is the observed cell a virus?
      the color of the virus is #33ff33
    */
    this.isVirus = function(blob, cell) {
      if (blob === null) {
          if (cell.isVirus()){return true;}
          else {return false;}
      }

      if (cell.isVirus() && this.compareSize(cell, blob, 1.2)) {
          return true;
      } else if (cell.isVirus() && cell.color.substring(3,5).toLowerCase() != "ff") {
          return true;
      }
      return false;
    };
    /*
      Check if a cell is considered food or not
      Anything that's below the 1.33 ratio or has a total size <= 13 is food!
    */
    this.isFood = function(blob, cell) {
        if (!cell.isVirus() && this.compareSize(cell, blob, STANDARD_RATIO) || (cell.size <= 13)) {
            return true;
        }
        return false;
    };
    /*
      Is the cell we're looking at a threat?
      If it's a bit bigger than us then it sure as hell is
    */
    this.isThreat = function(blob, cell) {

        if (!cell.isVirus() && this.compareSize(blob, cell, STANDARD_RATIO)) {
            return true;
        }
        return false;
    };

    /*
      </END: GAME RECEPTORS>
    */

    /*
      <START: MAIN BOT LOGIC>
    */

    /*
      Check if the cell is the player
      We don't want to add ourselves as potential food for example :)
    */
    this.isItMe = function(player, cell){
      for (var i = 0; i < player.length; i++) {
          if (cell.id == player[i].id) {
              return true;
          }
      }
      return false;
    };

    this.getAllObjects = function(that, listToUse, blob) {
        var foodElementList = [];
        var player = getPlayer();

        Object.keys(listToUse).forEach(function(element, index) {
            var identityCheck = that.isItMe(player, listToUse[element]);
            if (!identityCheck) {
                if (that.isFood(blob, listToUse[element]) && listToUse[element].isNotMoving()) {
                    foodElementList.push(listToUse[element]);
                }
            }
        });

        foodList = [];
        for (var i = 0; i < foodElementList.length; i++) {
            foodList.push([foodElementList[i].x, foodElementList[i].y, foodElementList[i].size]);
        }

        return [foodList];
    };

    this.getMasterRecord = function(blob) {
        var allDotsList = [];
        var player = getPlayer();
        var interNodes = getMemoryCells();

        allDotsList = this.getAllObjects(this, interNodes, blob);
        return dotList;
    };

    /*
      </END: MAIN BOT LOGIC>
    */

    /*
      The Main Loop
    */
    this.mainLoop = function() {
      var player = getPlayer();
      var interNodes = getMemoryCells();
      var tempMoveX = getPointX();
      var tempMoveY = getPointY();
      var botMoveChoice = [];

        if (player.length > 0) {
          for (var k = 0; k < player.length; k++) {
              if (true) {
                  drawPoint(player[k].x, player[k].y + player[k].size, 0, "" + (getLastUpdate() - player[k].birth) + " / " + (30000 + (player[k].birthMass * 57) - (getLastUpdate() - player[k].birth)) + " / " + player[k].birthMass);
              }
          }
          for (var k = 0; /*k < player.length*/ k < 1; k++) {
            var allObjects = this.getAllMasterRecord(player[k]);
            var allPossibleFood = allObjects[0];
            console.log(allPossibleFood);
          }
        }
      var temp1 = floor(Math.random()*100);
      var temp2 = floor(Math.random()*100);
      botMoveChoice = [temp1, temp2];
      console.log(botMoveChoice);
      return botMoveChoice;

    };
}

MFortuitumBot = new MFortuitum();
window.botList.push(MFortuitumBot);
console.log("Pushed MFortuitum");
window.updateBotList(); //This function might not exist yet.
<|endoftext|>


<commit_before>/**
 * Copyright 2014 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function StackedBar () {
	this.typeName = "stacked_bar";
	this.refresh = true;
	this.setupModalName = "#setupStackedBarChartModal";
	//Instance properties
	this.eventTypeFieldName = null;
	this.period = 0;
}

StackedBar.prototype = new Tile();

StackedBar.prototype.render = function(data, animate) {
    var parent = $("#content-for-" + this.id);

	var canvas = null;
	if(0 == parent.find(".chartcanvas").length) {
		canvas = $("
", {class: "chartcanvas"});
		parent.append(canvas);
	}
	else {
		canvas = parent.find(".chartcanvas");
	}
	if(!data.hasOwnProperty("trends")) {
		canvas.empty();
		return;
	}

    console.log(data);
    var colors = new Colors(Object.keys(data.trends).length);
    var d = [];
    var colorIdx = 0;
    for(var trend in data.trends) {
        var rows = [];
        rows.push(['date', 'count']);
        var trendData = data.trends[trend];
        for (var i = trendData.length - 1; i >= 0; i--) {
            rows.push([trendData[i].period, trendData[i].count]);
        };
        d.push({ data: rows, color: colors[colorIdx], label : trend, fill: true, fillColor: colors[colorIdx] });
        colorIdx++;
    }
    var timestamp = new Date().getTime();
    $.plot(canvas, d, {
            series: {
                stack: 0,
                lines: {show: true, fill: 1}
            },
            grid: {
                hoverable: true,
                color: "white",
                show: true
            },
            xaxis: {
                mode: "time",
                timezone: "browser",
                min: timestamp - (this.period * 60000),
                max: timestamp
            },
            selection : {
                mode: "x",
                minSize: 1
            },
            tooltip: true,
            tooltipOpts: {
                content: "%y events at %x",
                defaultFormat: true
            },
            legend: { show:true, position: 'nw', noColumns: 0, labelFormatter: function(label, series){
                return '  ' + label +'  ';
            }}
        });
	/*var colors = new Colors(Object.keys(data.result).length);
	var columns =[];
	for(property in data.result) {
		columns.push({label: property, data: data.result[property], color: colors.nextColor()});
	}
	var chartOptions = {
        series: {
            pie: {
                innerRadius: 0.8,
                show: true,
                label:{
                    show: false
                }
            }
        },
        legend : {
            show: false
        },
        grid: {
        	hoverable: true
        },
        tooltip: true,
        tooltipOpts: {
    		content: function(label, x, y) {
    			return label + ": " + y;
    		}
    	}
    };
    $.plot(canvas, columns, chartOptions);*/

	// var chart = c3.generate({
	// 	bindto: chartAreaId,
	// 	size: {
	// 		height: parentHeight,
	// 		width: parentWidth
	// 	},
	// 	data: {
	// 		columns: columns,
	// 		type : 'pie'
	// 	},
	// 	legend: {
	// 		show: false
	// 	},
	// 	transition: {
	// 		duration: transitionTime
	// 	}

	// });

};

StackedBar.prototype.getQuery = function() {
	if(this.eventTypeFieldName && this.period != 0) {
		var timestamp = new Date().getTime();
		return JSON.stringify({
			opcode : "trend",
			table : this.tables.selectedTable.name,
			filters : [{
				field: "_timestamp",
				operator: "between",
				temporal: true,
				from: (timestamp - (this.period * 60000)),
				to: timestamp
			}],
			field : this.eventTypeFieldName,
            period: "minutes"
		});
	}
};

StackedBar.prototype.isSetupDone = function() {
	return this.eventTypeFieldName && this.period != 0;
};

StackedBar.prototype.configChanged = function() {
	var modal = $(this.setupModalName);
	this.period = parseInt(modal.find(".refresh-period").val());
	this.eventTypeFieldName = modal.find(".stacked-bar-chart-field").val();
};

StackedBar.prototype.populateSetupDialog = function() {
	var modal = $(this.setupModalName);
	var select = modal.find(".stacked-bar-chart-field");
	select.find('option').remove();
	for (var i = this.tables.currentTableFieldMappings.length - 1; i >= 0; i--) {
		select.append('<option>' + this.tables.currentTableFieldMappings[i].field + '</option>');
	};
	if(this.eventTypeFieldName) {
		select.val(this.eventTypeFieldName);
	}
	select.selectpicker('refresh');
	modal.find(".refresh-period").val(( 0 != this.period)?this.period:"");
}

StackedBar.prototype.registerSpecificData = function(representation) {
	representation['period'] = this.period;
	representation['eventTypeFieldName'] = this.eventTypeFieldName;
};

StackedBar.prototype.loadSpecificData = function(representation) {
	this.period = representation['period'];
	this.eventTypeFieldName = representation['eventTypeFieldName'];
};<commit_msg>Stacked histogram fixed<commit_after>/**
 * Copyright 2014 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function StackedBar () {
	this.typeName = "stacked_bar";
	this.refresh = true;
	this.setupModalName = "#setupStackedBarChartModal";
	//Instance properties
	this.eventTypeFieldName = null;
	this.period = 0;
}

StackedBar.prototype = new Tile();

StackedBar.prototype.render = function(data, animate) {
    var parent = $("#content-for-" + this.id);

	var canvas = null;
	if(0 == parent.find(".chartcanvas").length) {
		canvas = $("
", {class: "chartcanvas"});
		parent.append(canvas);
	}
	else {
		canvas = parent.find(".chartcanvas");
	}
	if(!data.hasOwnProperty("trends")) {
		canvas.empty();
		return;
	}

    var colors = new Colors(Object.keys(data.trends).length);
    var d = [];
    var colorIdx = 0;
    var timestamp = new Date().getTime();
    var tmpData = new Object();
    for(var trend in data.trends) {
        var trendData = data.trends[trend];
        for (var i = 0; i < trendData.length; i++) {
            var time = trendData[i].period;
            var count = trendData[i].count;
            if(!tmpData.hasOwnProperty(time)) {
                tmpData[time] = new Object();
            }
            tmpData[time][trend] = count;
        }
    }
    //console.log(tmpData);
/*    for(var trend in data.trends) {
        var tmpData = new Object();
        var rows = [];
        rows.push(['date', 'count']);
        var trendData = data.trends[trend];
        for (var i = 0; i < trendData.length; i++) {
            tmpData[trendData[i].period] = trendData[i].count;
        };
        console.log(tmpData);
        for (var t = timestamp - (this.period * 60000); t <= timestamp; t += 60000) {
            if(tmpData.hasOwnProperty(t)) {
                rows.push([t, tmpData[t]]);
                console.log("Found: " + t);
            }
            else {
                rows.push([t, 0]);
            }
        }
        d.push({ data: rows, color: colors[colorIdx], label : trend, fill: true, fillColor: colors[colorIdx] });
        colorIdx++;
    }*/
    var trendWiseData = new Object();
    for(var time in tmpData) {
        for(var trend in data.trends) {
            var count = 0;
            var timeData = tmpData[time];
            if(timeData.hasOwnProperty(trend)) {
                count = timeData[trend];
                console.log("time found ");
            }
            var rows = null;
            if(!trendWiseData.hasOwnProperty(trend)) {
                rows = [];
                trendWiseData[trend] = rows;
            }
            rows = trendWiseData[trend];
            var timeVal = parseInt(time);
            rows.push([timeVal, count]);
        }
    }
    console.log(trendWiseData);
    for(var trend in trendWiseData) {
        var rows = trendWiseData[trend];
        rows.sort(function(lhs, rhs) {
            return (lhs[0] < rhs[0]) ? -1 : ((lhs[0] == rhs[0])? 0 : 1);
        })
        d.push({ data: rows, color: colors[colorIdx], label : trend, fill: true, fillColor: colors[colorIdx] });
    }
    $.plot(canvas, d, {
            series: {
                stack: true,
                lines: {show: true, fill: 1}
            },
            grid: {
                hoverable: true,
                color: "white",
                show: true
            },
            xaxis: {
                mode: "time",
                timezone: "browser",
                min: timestamp - (this.period * 60000),
                max: timestamp
            },
            selection : {
                mode: "x",
                minSize: 1
            },
            tooltip: true,
            tooltipOpts: {
                content: "%y events at %x",
                defaultFormat: true
            },
            legend: { show:true, position: 'nw', noColumns: 0, noRows: 0, labelFormatter: function(label, series){
                return '  ' + label +'  ';
            }}
        });
};

StackedBar.prototype.getQuery = function() {
	if(this.eventTypeFieldName && this.period != 0) {
		var timestamp = new Date().getTime();
		return JSON.stringify({
			opcode : "trend",
			table : this.tables.selectedTable.name,
			filters : [{
				field: "_timestamp",
				operator: "between",
				temporal: true,
				from: (timestamp - (this.period * 60000)),
				to: timestamp
			}],
			field : this.eventTypeFieldName,
            period: "minutes"
		});
	}
};

StackedBar.prototype.isSetupDone = function() {
	return this.eventTypeFieldName && this.period != 0;
};

StackedBar.prototype.configChanged = function() {
	var modal = $(this.setupModalName);
	this.period = parseInt(modal.find(".refresh-period").val());
	this.eventTypeFieldName = modal.find(".stacked-bar-chart-field").val();
};

StackedBar.prototype.populateSetupDialog = function() {
	var modal = $(this.setupModalName);
	var select = modal.find(".stacked-bar-chart-field");
	select.find('option').remove();
	for (var i = this.tables.currentTableFieldMappings.length - 1; i >= 0; i--) {
		select.append('<option>' + this.tables.currentTableFieldMappings[i].field + '</option>');
	};
	if(this.eventTypeFieldName) {
		select.val(this.eventTypeFieldName);
	}
	select.selectpicker('refresh');
	modal.find(".refresh-period").val(( 0 != this.period)?this.period:"");
}

StackedBar.prototype.registerSpecificData = function(representation) {
	representation['period'] = this.period;
	representation['eventTypeFieldName'] = this.eventTypeFieldName;
};

StackedBar.prototype.loadSpecificData = function(representation) {
	this.period = representation['period'];
	this.eventTypeFieldName = representation['eventTypeFieldName'];
};<|endoftext|>