<commit_before>(function(name, definition) {

/****************
 *  A tolerant, minimal icalendar parser
 *  (http://tools.ietf.org/html/rfc5545)
 *
 *  <peterbraden@peterbraden.co.uk>
 * **************/

  if (typeof module !== 'undefined') {
    module.exports = definition();
  } else if (typeof define === 'function' && typeof define.amd === 'object'){
    define(definition);
  } else {
    this[name] = definition();
  }

}('ical', function(){

   // Unescape Text re RFC 4.3.11
  var text = function(t){
    return (t
      .replace(/\\\,/g, ',')
      .replace(/\\\;/g, ';')
      .replace(/\\[nN]/g, '\n')
      .replace(/\\\\/g, '\\')
    )
  }

  var parseParams = function(p){
    var out = {}
    for (var i = 0; i<p.length; i++){
      if (p[i].indexOf('=') > -1){
        var segs = p[i].split('=');

        out[segs[0]] = parseValue(segs.slice(1).join('='));

      }
    }
    return out || sp
  }

  var parseValue = function(val){
    if ('TRUE' === val)
      return true;

    if ('FALSE' === val)
      return false;

    var number = Number(val);
    if (!isNaN(number))
      return number;

    return val;
  }

  var storeParam = function(name){
    return function(val, params, curr){
      var data;
      if (params && params.length && !(params.length==1 && params[0]==='CHARSET=utf-8')){
        data = {params:parseParams(params), val:text(val)}
      }
      else
        data = text(val)

      var current = curr[name];
      if (Array.isArray(current)){
        current.push(data);
        return curr;
      }

      if (current != null){
        curr[name] = [current, data];
        return curr;
      }

      curr[name] = data;
      return curr
    }
  }

  var addTZ = function(dt, name, params){
    var p = parseParams(params);

    if (params && p){
      dt[name].tz = p.TZID
    }

    return dt
  }

  var dateParam = function(name){
    return function(val, params, curr){

      // Store as string - worst case scenario
      storeParam(name)(val, undefined, curr)

      if (params && params[0] === "VALUE=DATE") {
        // Just Date

        var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
        if (comps !== null) {
          // No TZ info - assume same timezone as this computer
          curr[name] = new Date(
            comps[1],
            parseInt(comps[2], 10)-1,
            comps[3]
          );

          return addTZ(curr, name, params);
        }
      }

      //typical RFC date-time format
      var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
      if (comps !== null) {
        if (comps[7] == 'Z'){ // GMT
          curr[name] = new Date(Date.UTC(
            parseInt(comps[1], 10),
            parseInt(comps[2], 10)-1,
            parseInt(comps[3], 10),
            parseInt(comps[4], 10),
            parseInt(comps[5], 10),
            parseInt(comps[6], 10 )
          ));
          // TODO add tz
        } else {
          curr[name] = new Date(
            parseInt(comps[1], 10),
            parseInt(comps[2], 10)-1,
            parseInt(comps[3], 10),
            parseInt(comps[4], 10),
            parseInt(comps[5], 10),
            parseInt(comps[6], 10)
          );
        }
      }

      return addTZ(curr, name, params)
    }
  }

  var geoParam = function(name){
    return function(val, params, curr){
      storeParam(val, params, curr)
      var parts = val.split(';');
      curr[name] = {lat:Number(parts[0]), lon:Number(parts[1])};
      return curr
    }
  }

  var categoriesParam = function (name) {
    var separatorPattern = /\s*,\s*/g;
    return function (val, params, curr) {
      storeParam(val, params, curr)
      curr[name] = val ? val.split(separatorPattern) : []
      return curr
    }
  }

  var addFBType = function(fb, params){
    var p = parseParams(params);

    if (params && p){
      fb.type = p.FBTYPE || "BUSY"
    }

    return fb;
  }

  var freebusyParam = function (name) {
    return function(val, params, curr){
      var fb = addFBType({}, params);
      curr[name] = curr[name] || []
      curr[name].push(fb);

      storeParam(val, params, fb);

      var parts = val.split('/');

      ['start', 'end'].forEach(function (name, index) {
        dateParam(name)(parts[index], params, fb);
      });

      return curr;
    }
  }

  return {

    objectHandlers : {
      'BEGIN' : function(component, params, curr, stack){
          stack.push(curr)

          return {type:component, params:params}
        }

      , 'END' : function(component, params, curr, stack){
        // prevents the need to search the root of the tree for the VCALENDAR object
        if (component === "VCALENDAR") {
            //scan all high level object in curr and drop all strings
            var key,
                obj;

            for (key in curr) {
                if(curr.hasOwnProperty(key)) {
                   obj = curr[key];
                   if (typeof obj === 'string') {
                       delete curr[key];
                   }
                }
            }

            return curr
        }

        var par = stack.pop()

        if (curr.uid)
          par[curr.uid] = curr
        else
          par[Math.random()*100000] = curr  // Randomly assign ID : TODO - use true GUID

        return par
      }

      , 'SUMMARY' : storeParam('summary')
      , 'DESCRIPTION' : storeParam('description')
      , 'URL' : storeParam('url')
      , 'UID' : storeParam('uid')
      , 'LOCATION' : storeParam('location')
      , 'DTSTART' : dateParam('start')
      , 'DTEND' : dateParam('end')
      ,' CLASS' : storeParam('class')
      , 'TRANSP' : storeParam('transparency')
      , 'GEO' : geoParam('geo')
      , 'PERCENT-COMPLETE': storeParam('completion')
      , 'COMPLETED': dateParam('completed')
      , 'CATEGORIES': categoriesParam('categories')
      , 'FREEBUSY': freebusyParam('freebusy')
    },

    handleObject : function(name, val, params, ctx, stack, line){
      var self = this

      if(self.objectHandlers[name])
        return self.objectHandlers[name](val, params, ctx, stack, line)

      //handling custom properties
      if(name.match(/X\-[\w\-]+/) && stack.length > 0) {
          //trimming the leading and perform storeParam
          name = name.substring(2);
          return (storeParam(name))(val, params, ctx, stack, line);
      }

      return storeParam(name.toLowerCase())(val, params, ctx);
    },

    parseICS : function(str){
      var self = this
      var lines = str.split(/\r?\n/)
      var ctx = {}
      var stack = []

      for (var i = 0, ii = lines.length, l = lines[0]; i<ii; i++, l=lines[i]){
        //Unfold : RFC#3.1
        while (lines[i+1] && /[ \t]/.test(lines[i+1][0])) {
          l += lines[i+1].slice(1)
          i += 1
        }

        var kv = l.split(":")

        if (kv.length < 2){
          // Invalid line - must have k&v
          continue;
        }

        // Although the spec says that vals with colons should be quote wrapped
        // in practise nobody does, so we assume further colons are part of the
        // val
        var value = kv.slice(1).join(":")
          , kp = kv[0].split(";")
          , name = kp[0]
          , params = kp.slice(1)

        ctx = self.handleObject(name, value, params, ctx, stack, l) || {}
      }

       // type and params are added to the list of items, get rid of them.
       delete ctx.type
       delete ctx.params

       return ctx
    }

  }
}))
<commit_msg>Preventing parsing from failing when not provided FREEBUSY dates<commit_after>(function(name, definition) {

/****************
 *  A tolerant, minimal icalendar parser
 *  (http://tools.ietf.org/html/rfc5545)
 *
 *  <peterbraden@peterbraden.co.uk>
 * **************/

  if (typeof module !== 'undefined') {
    module.exports = definition();
  } else if (typeof define === 'function' && typeof define.amd === 'object'){
    define(definition);
  } else {
    this[name] = definition();
  }

}('ical', function(){

   // Unescape Text re RFC 4.3.11
  var text = function(t){
    t = t || "";
    return (t
      .replace(/\\\,/g, ',')
      .replace(/\\\;/g, ';')
      .replace(/\\[nN]/g, '\n')
      .replace(/\\\\/g, '\\')
    )
  }

  var parseParams = function(p){
    var out = {}
    for (var i = 0; i<p.length; i++){
      if (p[i].indexOf('=') > -1){
        var segs = p[i].split('=');

        out[segs[0]] = parseValue(segs.slice(1).join('='));

      }
    }
    return out || sp
  }

  var parseValue = function(val){
    if ('TRUE' === val)
      return true;

    if ('FALSE' === val)
      return false;

    var number = Number(val);
    if (!isNaN(number))
      return number;

    return val;
  }

  var storeParam = function(name){
    return function(val, params, curr){
      var data;
      if (params && params.length && !(params.length==1 && params[0]==='CHARSET=utf-8')){
        data = {params:parseParams(params), val:text(val)}
      }
      else
        data = text(val)

      var current = curr[name];
      if (Array.isArray(current)){
        current.push(data);
        return curr;
      }

      if (current != null){
        curr[name] = [current, data];
        return curr;
      }

      curr[name] = data;
      return curr
    }
  }

  var addTZ = function(dt, name, params){
    var p = parseParams(params);

    if (params && p){
      dt[name].tz = p.TZID
    }

    return dt
  }

  var dateParam = function(name){
    return function(val, params, curr){

      // Store as string - worst case scenario
      storeParam(name)(val, undefined, curr)

      if (params && params[0] === "VALUE=DATE") {
        // Just Date

        var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
        if (comps !== null) {
          // No TZ info - assume same timezone as this computer
          curr[name] = new Date(
            comps[1],
            parseInt(comps[2], 10)-1,
            comps[3]
          );

          return addTZ(curr, name, params);
        }
      }

      //typical RFC date-time format
      var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
      if (comps !== null) {
        if (comps[7] == 'Z'){ // GMT
          curr[name] = new Date(Date.UTC(
            parseInt(comps[1], 10),
            parseInt(comps[2], 10)-1,
            parseInt(comps[3], 10),
            parseInt(comps[4], 10),
            parseInt(comps[5], 10),
            parseInt(comps[6], 10 )
          ));
          // TODO add tz
        } else {
          curr[name] = new Date(
            parseInt(comps[1], 10),
            parseInt(comps[2], 10)-1,
            parseInt(comps[3], 10),
            parseInt(comps[4], 10),
            parseInt(comps[5], 10),
            parseInt(comps[6], 10)
          );
        }
      }

      return addTZ(curr, name, params)
    }
  }

  var geoParam = function(name){
    return function(val, params, curr){
      storeParam(val, params, curr)
      var parts = val.split(';');
      curr[name] = {lat:Number(parts[0]), lon:Number(parts[1])};
      return curr
    }
  }

  var categoriesParam = function (name) {
    var separatorPattern = /\s*,\s*/g;
    return function (val, params, curr) {
      storeParam(val, params, curr)
      curr[name] = val ? val.split(separatorPattern) : []
      return curr
    }
  }

  var addFBType = function(fb, params){
    var p = parseParams(params);

    if (params && p){
      fb.type = p.FBTYPE || "BUSY"
    }

    return fb;
  }

  var freebusyParam = function (name) {
    return function(val, params, curr){
      var fb = addFBType({}, params);
      curr[name] = curr[name] || []
      curr[name].push(fb);

      storeParam(val, params, fb);

      var parts = val.split('/');

      ['start', 'end'].forEach(function (name, index) {
        dateParam(name)(parts[index], params, fb);
      });

      return curr;
    }
  }

  return {

    objectHandlers : {
      'BEGIN' : function(component, params, curr, stack){
          stack.push(curr)

          return {type:component, params:params}
        }

      , 'END' : function(component, params, curr, stack){
        // prevents the need to search the root of the tree for the VCALENDAR object
        if (component === "VCALENDAR") {
            //scan all high level object in curr and drop all strings
            var key,
                obj;

            for (key in curr) {
                if(curr.hasOwnProperty(key)) {
                   obj = curr[key];
                   if (typeof obj === 'string') {
                       delete curr[key];
                   }
                }
            }

            return curr
        }

        var par = stack.pop()

        if (curr.uid)
          par[curr.uid] = curr
        else
          par[Math.random()*100000] = curr  // Randomly assign ID : TODO - use true GUID

        return par
      }

      , 'SUMMARY' : storeParam('summary')
      , 'DESCRIPTION' : storeParam('description')
      , 'URL' : storeParam('url')
      , 'UID' : storeParam('uid')
      , 'LOCATION' : storeParam('location')
      , 'DTSTART' : dateParam('start')
      , 'DTEND' : dateParam('end')
      ,' CLASS' : storeParam('class')
      , 'TRANSP' : storeParam('transparency')
      , 'GEO' : geoParam('geo')
      , 'PERCENT-COMPLETE': storeParam('completion')
      , 'COMPLETED': dateParam('completed')
      , 'CATEGORIES': categoriesParam('categories')
      , 'FREEBUSY': freebusyParam('freebusy')
    },

    handleObject : function(name, val, params, ctx, stack, line){
      var self = this

      if(self.objectHandlers[name])
        return self.objectHandlers[name](val, params, ctx, stack, line)

      //handling custom properties
      if(name.match(/X\-[\w\-]+/) && stack.length > 0) {
          //trimming the leading and perform storeParam
          name = name.substring(2);
          return (storeParam(name))(val, params, ctx, stack, line);
      }

      return storeParam(name.toLowerCase())(val, params, ctx);
    },

    parseICS : function(str){
      var self = this
      var lines = str.split(/\r?\n/)
      var ctx = {}
      var stack = []

      for (var i = 0, ii = lines.length, l = lines[0]; i<ii; i++, l=lines[i]){
        //Unfold : RFC#3.1
        while (lines[i+1] && /[ \t]/.test(lines[i+1][0])) {
          l += lines[i+1].slice(1)
          i += 1
        }

        var kv = l.split(":")

        if (kv.length < 2){
          // Invalid line - must have k&v
          continue;
        }

        // Although the spec says that vals with colons should be quote wrapped
        // in practise nobody does, so we assume further colons are part of the
        // val
        var value = kv.slice(1).join(":")
          , kp = kv[0].split(";")
          , name = kp[0]
          , params = kp.slice(1)

        ctx = self.handleObject(name, value, params, ctx, stack, l) || {}
      }

       // type and params are added to the list of items, get rid of them.
       delete ctx.type
       delete ctx.params

       return ctx
    }

  }
}))
<|endoftext|>


<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 currentTopicId;
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) {
			// Find random topic to pester
			var $ = cheerio.load(body);
			var randomTopic = Math.floor(Math.random() * 50) + 1;
			var href;

			// I don't understand the cheerio library. This is ridiculous. I don't care.
			$('td.oh div.fl a').each((index, element) => {

				if (index === randomTopic) {
					href = "https:" + $(element).attr('href');
					var topicNumberRegex = href.match(/(topic=)([0-9]+)/);

					if (topicNumberRegex) {
						currentTopicId = topicRegex[0];
						return false;
					}

					else {
						// Avoid potential for infinite loop here
						return false;
					}
				}

			});

			app.getMessageList(href);
		}

		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"]').attr('value');
			console.log(currentToken);
			app.contributeToDiscussion();
		}

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

	});
};

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

	var formData = {};
			formData.topic = currentTopicId;
			formData.h = currentToken;
			formData.message = app.generateMarkovChain(true); // Pass true to get return value immediately

	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>Fix typo<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 currentTopicId;
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) {
			// Find random topic to pester
			var $ = cheerio.load(body);
			var randomTopic = Math.floor(Math.random() * 50) + 1;
			var href;

			// I don't understand the cheerio library. This is ridiculous. I don't care.
			$('td.oh div.fl a').each((index, element) => {

				if (index === randomTopic) {
					href = "https:" + $(element).attr('href');
					var topicNumberRegex = href.match(/(topic=)([0-9]+)/);

					if (topicNumberRegex) {
						currentTopicId = topicNumberRegex[0];
						return false;
					}

					else {
						// Avoid potential for infinite loop here
						return false;
					}
				}

			});

			app.getMessageList(href);
		}

		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"]').attr('value');
			console.log(currentToken);
			app.contributeToDiscussion();
		}

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

	});
};

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

	var formData = {};
			formData.topic = currentTopicId;
			formData.h = currentToken;
			formData.message = app.generateMarkovChain(true); // Pass true to get return value immediately

	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 address = {
        city: ["deliveryCity", "quickDeliveryCity"],
        address: [
            "deliveryAddress",
            "quickDeliveryAddress",
            "quickDeliveryAddressStreet"
        ],
        postal: ["quickDeliveryZipCode", "deliveryZipCode"]
    };

    function executeEach(idList, action) {
        idList.forEach(function(id) {
            var $el = $("#" + id);
            if ($el.length) {
                action($el);
            }
        });
    }

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

    function initSuggestions() {
        for (var type in fields) {
            initSuggestionsForType(type, fields[type]);
        }
    }

    function initSuggestionsForType(type, idList) {
        executeEach(idList, function($el) {
            $el.suggestions({
                serviceUrl: SERVICE_URL,
                token: TOKEN,
                partner: PARTNER,
                type: type.toUpperCase()
            });
        });
    }

    function initDelivery() {
        suggestCity(address.city, address.address, address.postal);
        suggestPostalCode(address.address, address.postal);
    }

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

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

    // suggest city and restrict address accordingly
    // also fill postal code
    function suggestCity(cityList, addressList, postalList) {
        executeEach(cityList, function($city) {
            $city.suggestions({
                serviceUrl: SERVICE_URL,
                token: TOKEN,
                partner: PARTNER,
                type: "ADDRESS",
                hint: false,
                bounds: "city-settlement",
                onSelect: function(suggestion) {
                    setAddressConstraint(addressList, suggestion.data.kladr_id);
                    setPostalCode(
                        postalList,
                        "" || suggestion.data.postal_code
                    );
                },
                onSelectNothing: function() {
                    clearAddressConstraint(addressList);
                    setPostalCode(postalList, "");
                }
            });
        });
    }

    function setPostalCode(postalList, value) {
        executeEach(postalList, function($postal) {
            $postal.val(value);
        });
    }

    // fill postal code when address is selected
    function suggestPostalCode(addressList, postalList) {
        executeEach(addressList, function($address) {
            $address.suggestions().setOptions({
                onSelect: function(suggestion) {
                    setPostalCode(
                        postalList,
                        "" || suggestion.data.postal_code
                    );
                },
                onSelectNothing: function() {
                    setPostalCode(postalList, "");
                }
            });
        });
    }

    $(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",
            "quickDeliveryAddressStreet",
            "sites_client_addr"
        ]
    };

    var address = {
        city: ["deliveryCity", "quickDeliveryCity"],
        address: [
            "deliveryAddress",
            "quickDeliveryAddress",
            "quickDeliveryAddressStreet"
        ],
        postal: ["quickDeliveryZipCode", "deliveryZipCode"]
    };

    function executeEach(idList, action) {
        idList.forEach(function(id) {
            var $el = $("#" + id);
            if ($el.length) {
                action($el);
            }
        });
    }

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

    function initSuggestions() {
        for (var type in fields) {
            initSuggestionsForType(type, fields[type]);
        }
    }

    function initSuggestionsForType(type, idList) {
        executeEach(idList, function($el) {
            $el.suggestions({
                serviceUrl: SERVICE_URL,
                token: TOKEN,
                partner: PARTNER,
                type: type.toUpperCase()
            });
        });
    }

    function initDelivery() {
        suggestCity(address.city, address.address, address.postal);
        suggestPostalCode(address.address, address.postal);
    }

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

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

    // suggest city and restrict address accordingly
    // also fill postal code
    function suggestCity(cityList, addressList, postalList) {
        executeEach(cityList, function($city) {
            $city.suggestions({
                serviceUrl: SERVICE_URL,
                token: TOKEN,
                partner: PARTNER,
                type: "ADDRESS",
                hint: false,
                bounds: "city-settlement",
                onSelect: function(suggestion) {
                    setAddressConstraint(addressList, suggestion.data.kladr_id);
                    setPostalCode(
                        postalList,
                        "" || suggestion.data.postal_code
                    );
                },
                onSelectNothing: function() {
                    clearAddressConstraint(addressList);
                    setPostalCode(postalList, "");
                }
            });
        });
    }

    function setPostalCode(postalList, value) {
        executeEach(postalList, function($postal) {
            $postal.val(value);
        });
    }

    // fill postal code when address is selected
    function suggestPostalCode(addressList, postalList) {
        executeEach(addressList, function($address) {
            $address.suggestions().setOptions({
                onSelect: function(suggestion) {
                    setPostalCode(
                        postalList,
                        "" || suggestion.data.postal_code
                    );
                },
                onSelectNothing: function() {
                    setPostalCode(postalList, "");
                }
            });
        });
    }

    $(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>import React from 'react';
import {routerShape} from 'react-router';

import Alert from '../../../../../../src/js/components/Alert';
import DateUtil from '../../../../../../src/js/utils/DateUtil';
import ConfigurationMap from '../../../../../../src/js/components/ConfigurationMap';
import ConfigurationMapHeading from '../../../../../../src/js/components/ConfigurationMapHeading';
import ConfigurationMapSection from '../../../../../../src/js/components/ConfigurationMapSection';
import DeclinedOffersHelpText from '../../constants/DeclinedOffersHelpText';
import DeclinedOffersTable from '../../components/DeclinedOffersTable';
import DeclinedOffersUtil from '../../utils/DeclinedOffersUtil';
import HashMapDisplay from '../../../../../../src/js/components/HashMapDisplay';
import MarathonStore from '../../stores/MarathonStore';
import Pod from '../../structs/Pod';
import PodContainerTerminationTable from './PodContainerTerminationTable';
import RecentOffersSummary from '../../components/RecentOffersSummary';
import TimeAgo from '../../../../../../src/js/components/TimeAgo';

const METHODS_TO_BIND = ['handleJumpToRecentOffersClick'];

class PodDebugTabView extends React.Component {
  constructor() {
    super(...arguments);

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    MarathonStore.setShouldEmbedLastUnusedOffers(true);
  }

  componentWillUnmount() {
    MarathonStore.setShouldEmbedLastUnusedOffers(false);
  }

  getDeclinedOffersTable() {
    const {pod} = this.props;
    const queue = pod.getQueue();

    if (!DeclinedOffersUtil.shouldDisplayDeclinedOffersWarning(queue)
      || queue.declinedOffers.offers == null) {
      return null;
    }

    return (

        <ConfigurationMapHeading level={2}>
          Details
        </ConfigurationMapHeading>
        <DeclinedOffersTable offers={queue.declinedOffers.offers}
          service={pod}
          summary={queue.declinedOffers.summary} />

    );
  }

  getTerminationHistory() {
    const history = this.props.pod.getTerminationHistoryList().getItems();
    if (!history.length) {
      return (
        <ConfigurationMapSection>
          <ConfigurationMapHeading>
            Last Terminations
          </ConfigurationMapHeading>

(No data)

        </ConfigurationMapSection>
      );
    }

    return history.reduce(function (acc, item, index) {
      let headline;
      const startedAt = item.getStartedAt();
      const terminatedAt = item.getTerminatedAt();
      const terminationValueMapping = {
        'Instance ID': item.getId(),
        'Message': item.getMessage(),
        'Started At': (

            {startedAt.toString()} (<TimeAgo time={startedAt} />)

        )
      };

      if (index === 0) {
        headline = (
          <ConfigurationMapHeading level={2}>
            Last Termination (<TimeAgo time={terminatedAt} />)
          </ConfigurationMapHeading>
        );
      } else {
        headline = (
          <ConfigurationMapHeading level={2}>
            Terminated at {terminatedAt.toString()} (<TimeAgo time={terminatedAt} />)
          </ConfigurationMapHeading>
        );
      }

      acc.push(
        <ConfigurationMapSection key={`termination-${index}`}>
          {headline}
          <HashMapDisplay hash={terminationValueMapping} />
        </ConfigurationMapSection>,
        <ConfigurationMapSection key={`container-${index}`}>
          <ConfigurationMapHeading level={3}>
            Containers
          </ConfigurationMapHeading>
          <PodContainerTerminationTable containers={item.getContainers()} />
        </ConfigurationMapSection>
      );

      return acc;
    }, []);
  }

  getLastVersionChange() {
    const {pod} = this.props;
    const lastUpdated = pod.getLastUpdated();

    // Note to reader: `getLastChanged` refers to the last changes that happened
    // to the pod (such as state changes or instance changes), but we are
    // interested in the last configuration update, for which we are using the
    // `getLastUpdate` function.

    const LastVersionChangeValueMapping = {
      'Configuration': (

          {lastUpdated.toString()} (<TimeAgo time={lastUpdated} />)

      )
    };

    return (
      <ConfigurationMapSection>
        <ConfigurationMapHeading>
          Last Changes
        </ConfigurationMapHeading>
        <HashMapDisplay hash={LastVersionChangeValueMapping} />
      </ConfigurationMapSection>
    );
  }

  getRecentOfferSummary() {
    const queue = this.props.pod.getQueue();
    let introText = null;
    let mainContent = null;
    let offerCount = null;

    if (!DeclinedOffersUtil.shouldDisplayDeclinedOffersWarning(queue)
      || queue.declinedOffers.summary == null) {
      introText = 'Offers will appear here when your service is deploying or waiting for resources.';
    } else {
      const {declinedOffers: {summary}} = queue;
      const {roles: {offers = 0}} = summary;

      introText = DeclinedOffersHelpText.summaryIntro;

      mainContent = (

          <ConfigurationMapHeading level={2}>
            Summary
          </ConfigurationMapHeading>
          <RecentOffersSummary data={summary} />

      );

      offerCount = ` (${offers})`;
    }

    return (

 { this.offerSummaryRef = ref; }}>
        <ConfigurationMapHeading>
          Recent Resource Offers{offerCount}
        </ConfigurationMapHeading>

{introText}

        {mainContent}

    );
  }

  getWaitingForResourcesNotice() {
    const queue = this.props.pod.getQueue();

    if (queue == null || queue.since == null) {
      return null;
    }

    const waitingSince = DateUtil.strToMs(queue.since);
    const timeWaiting = Date.now() - waitingSince;

    // If the service has been waiting for less than five minutes, we don't
    // display the warning.
    if (timeWaiting < 1000 * 60 * 5) {
      return null;
    }

    return (
      <Alert>
        {'DC/OS has been waiting for resources and is unable to complete this deployment for '}
        {DateUtil.getDuration(timeWaiting, null)}{'. '}

          See recent resource offers
        .
      </Alert>
    );
  }

  handleJumpToRecentOffersClick() {
    if (this.offerSummaryRef) {
      this.offerSummaryRef.scrollIntoView();
    }
  }

  render() {
    return (

        {this.getWaitingForResourcesNotice()}
        <ConfigurationMap>
          {this.getLastVersionChange()}
          {this.getTerminationHistory()}
          {this.getRecentOfferSummary()}
          {this.getDeclinedOffersTable()}
        </ConfigurationMap>

    );
  }
}

PodDebugTabView.contextTypes = {
  router: routerShape
};

PodDebugTabView.propTypes = {
  pod: React.PropTypes.instanceOf(Pod)
};

module.exports = PodDebugTabView;
<commit_msg>Use ConfigurationMap in PodDebugContainer<commit_after>import React from 'react';
import {routerShape} from 'react-router';

import Alert from '../../../../../../src/js/components/Alert';
import DateUtil from '../../../../../../src/js/utils/DateUtil';
import ConfigurationMap from '../../../../../../src/js/components/ConfigurationMap';
import ConfigurationMapHeading from '../../../../../../src/js/components/ConfigurationMapHeading';
import ConfigurationMapLabel from '../../../../../../src/js/components/ConfigurationMapLabel';
import ConfigurationMapRow from '../../../../../../src/js/components/ConfigurationMapRow';
import ConfigurationMapSection from '../../../../../../src/js/components/ConfigurationMapSection';
import ConfigurationMapValue from '../../../../../../src/js/components/ConfigurationMapValue';
import DeclinedOffersHelpText from '../../constants/DeclinedOffersHelpText';
import DeclinedOffersTable from '../../components/DeclinedOffersTable';
import DeclinedOffersUtil from '../../utils/DeclinedOffersUtil';
import MarathonStore from '../../stores/MarathonStore';
import Pod from '../../structs/Pod';
import PodContainerTerminationTable from './PodContainerTerminationTable';
import RecentOffersSummary from '../../components/RecentOffersSummary';
import TimeAgo from '../../../../../../src/js/components/TimeAgo';

const METHODS_TO_BIND = ['handleJumpToRecentOffersClick'];

class PodDebugTabView extends React.Component {
  constructor() {
    super(...arguments);

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    MarathonStore.setShouldEmbedLastUnusedOffers(true);
  }

  componentWillUnmount() {
    MarathonStore.setShouldEmbedLastUnusedOffers(false);
  }

  getDeclinedOffersTable() {
    const {pod} = this.props;
    const queue = pod.getQueue();

    if (!DeclinedOffersUtil.shouldDisplayDeclinedOffersWarning(queue)
      || queue.declinedOffers.offers == null) {
      return null;
    }

    return (

        <ConfigurationMapHeading level={2}>
          Details
        </ConfigurationMapHeading>
        <DeclinedOffersTable offers={queue.declinedOffers.offers}
          service={pod}
          summary={queue.declinedOffers.summary} />

    );
  }

  getTerminationHistory() {
    const history = this.props.pod.getTerminationHistoryList().getItems();
    if (!history.length) {
      return (
        <ConfigurationMapSection>
          <ConfigurationMapHeading>
            Last Terminations
          </ConfigurationMapHeading>

(No data)

        </ConfigurationMapSection>
      );
    }

    return history.reduce(function (acc, item, index) {
      let headline;
      const startedAt = item.getStartedAt();
      const terminatedAt = item.getTerminatedAt();

      if (index === 0) {
        headline = (
          <ConfigurationMapHeading level={2}>
            Last Termination (<TimeAgo time={terminatedAt} />)
          </ConfigurationMapHeading>
        );
      } else {
        headline = (
          <ConfigurationMapHeading level={2}>
            Terminated at {terminatedAt.toString()} (<TimeAgo time={terminatedAt} />)
          </ConfigurationMapHeading>
        );
      }

      acc.push(
        <ConfigurationMapSection key={`termination-${index}`}>
          {headline}
          <ConfigurationMapSection>
            <ConfigurationMapRow>
              <ConfigurationMapLabel>
                Instance ID
              </ConfigurationMapLabel>
              <ConfigurationMapValue>
                {item.getId()}
              </ConfigurationMapValue>
            </ConfigurationMapRow>
            <ConfigurationMapRow>
              <ConfigurationMapLabel>
                Message
              </ConfigurationMapLabel>
              <ConfigurationMapValue>
                {item.getMessage()}
              </ConfigurationMapValue>
            </ConfigurationMapRow>
            <ConfigurationMapRow>
              <ConfigurationMapLabel>
                Started At
              </ConfigurationMapLabel>
              <ConfigurationMapValue>
                {startedAt.toString()} (<TimeAgo time={startedAt} />)
              </ConfigurationMapValue>
            </ConfigurationMapRow>
          </ConfigurationMapSection>
        </ConfigurationMapSection>,
        <ConfigurationMapSection key={`container-${index}`}>
          <ConfigurationMapHeading level={3}>
            Containers
          </ConfigurationMapHeading>
          <PodContainerTerminationTable containers={item.getContainers()} />
        </ConfigurationMapSection>
      );

      return acc;
    }, []);
  }

  getLastVersionChange() {
    const {pod} = this.props;
    const lastUpdated = pod.getLastUpdated();

    // Note to reader: `getLastChanged` refers to the last changes that happened
    // to the pod (such as state changes or instance changes), but we are
    // interested in the last configuration update, for which we are using the
    // `getLastUpdate` function.

    return (
      <ConfigurationMapSection>
        <ConfigurationMapHeading>
          Last Changes
        </ConfigurationMapHeading>
        <ConfigurationMapRow>
          <ConfigurationMapLabel>
            Configuration
          </ConfigurationMapLabel>
          <ConfigurationMapValue>
            {lastUpdated.toString()} (<TimeAgo time={lastUpdated} />)
          </ConfigurationMapValue>
        </ConfigurationMapRow>
      </ConfigurationMapSection>
    );
  }

  getRecentOfferSummary() {
    const queue = this.props.pod.getQueue();
    let introText = null;
    let mainContent = null;
    let offerCount = null;

    if (!DeclinedOffersUtil.shouldDisplayDeclinedOffersWarning(queue)
      || queue.declinedOffers.summary == null) {
      introText = 'Offers will appear here when your service is deploying or waiting for resources.';
    } else {
      const {declinedOffers: {summary}} = queue;
      const {roles: {offers = 0}} = summary;

      introText = DeclinedOffersHelpText.summaryIntro;

      mainContent = (

          <ConfigurationMapHeading level={2}>
            Summary
          </ConfigurationMapHeading>
          <RecentOffersSummary data={summary} />

      );

      offerCount = ` (${offers})`;
    }

    return (

 { this.offerSummaryRef = ref; }}>
        <ConfigurationMapHeading>
          Recent Resource Offers{offerCount}
        </ConfigurationMapHeading>

{introText}

        {mainContent}

    );
  }

  getWaitingForResourcesNotice() {
    const queue = this.props.pod.getQueue();

    if (queue == null || queue.since == null) {
      return null;
    }

    const waitingSince = DateUtil.strToMs(queue.since);
    const timeWaiting = Date.now() - waitingSince;

    // If the service has been waiting for less than five minutes, we don't
    // display the warning.
    if (timeWaiting < 1000 * 60 * 5) {
      return null;
    }

    return (
      <Alert>
        {'DC/OS has been waiting for resources and is unable to complete this deployment for '}
        {DateUtil.getDuration(timeWaiting, null)}{'. '}

          See recent resource offers
        .
      </Alert>
    );
  }

  handleJumpToRecentOffersClick() {
    if (this.offerSummaryRef) {
      this.offerSummaryRef.scrollIntoView();
    }
  }

  render() {
    return (

        {this.getWaitingForResourcesNotice()}
        <ConfigurationMap>
          {this.getLastVersionChange()}
          {this.getTerminationHistory()}
          {this.getRecentOfferSummary()}
          {this.getDeclinedOffersTable()}
        </ConfigurationMap>

    );
  }
}

PodDebugTabView.contextTypes = {
  router: routerShape
};

PodDebugTabView.propTypes = {
  pod: React.PropTypes.instanceOf(Pod)
};

module.exports = PodDebugTabView;
<|endoftext|>


<commit_before>com.magadanski.mSimpleNav.DataList;

//////////////////////////////////////
// DataList Class Implementation /////
//////////////////////////////////////
(function () {
	// import Class
	var that;
	var DataList = function () {
		that = this;
	}
	DataList.inherits(com.magadanski.EventDispatcher);
	com.magadanski.mSimpleNav.DataList = DataList;

	// private properties

	// public properties

	// private methods

	// public methods

})();
<commit_msg>DataList base class implementation ready<commit_after>com.magadanski.mSimpleNav.DataList;

//////////////////////////////////////
// DataList Class Implementation /////
//////////////////////////////////////
(function () {
	// import Class
	var that;
	var DataList = function (options) {
		that = this;

		if (typeof(options) != 'object') {
			options = {};
		}

		if (typeof(options.startList) != 'function') {
			options.startList = function () {
				return '
';
			}
		}

		if (typeof(options.endList) != 'function') {
			options.endList = function () {
				return '
';
			}
		}

		if (typeof(options.renderItem) != 'function') {
			options.renderItem = function (item) {
				return '
' + item + '
';
			}
		}

		// priviledged properties
		that.data = [];

		// priviledged methods
		that.startList = options.startList;
		that.endList = options.endList;
		that.renderItem = options.renderItem;
	}
	DataList.inherits(com.magadanski.EventDispatcher);
	com.magadanski.mSimpleNav.DataList = DataList;

	// private properties

	// public properties

	// private methods

	// public methods
	DataList.prototype.render = function () {
		var markup = '';

		markup += that.startList();

		for (var i in that.data) {
			markup += that.renderItem(that.data[i]);
		}

		markup += that.endList();

		return markup;
	}
})();
<|endoftext|>


<commit_before>define([
	'jquery',
	'Global/SQ',
	'SQ/Broadcaster',
	'SQ/Util',
	'underscore',
	'text!./template/schedule_table.tmpl',
	'datatables',
	'jqueryui'
], function(
	$,
	SQ,
	Broadcaster,
	Util,
	_,
	ScheduleTableTemplate
) {
	'use strict';

	return function ScheduleTab() {

		var _me = this;
		var _util = new Util();
		var _$schedule_tab = null;
		var _schedule_table = null;
		var _add_schedule_data = {};

		SQ.mixin(_me, new Broadcaster(['add_schedule', 'delete_schedule']));

		(function _init() {
		})();

		this.initialize = function($e) {
			_$schedule_tab = $e;
			$e.find('#add-schedule-form [name="date"]').datepicker({
				dateFormat: 'yy-mm-dd'
			});

			_$schedule_tab.find('#schedule-table-container').append(_.template(ScheduleTableTemplate));
			_schedule_table = _$schedule_tab.find('#schedule-table').DataTable({
				bProcessing: true,
				fnDrawCallback: function(oSettings) {
				}
			});

			_$schedule_tab.find('#add-schedule-form').validate({
				rules: {
					term_id: {
						required: true
					},
					subject_id: {
						required: true
					},
					date: {
						required: true,
						remote: {
							url: '/ajax/schedule/dateNotExist',
							type: 'post',
							data: {classroom_id: $e.find('[name="classroom_id"]').val()}
						}
					}
				},
				messages: {
					date: {
						remote: 'There is already a schedule exists on this date'
					}
				},
				submitHandler: function(form) {
					_add_schedule_data = _util.serializeJSON($(form));
					_me.broadcast('add_schedule', _add_schedule_data);
				}
			});

			_setListeners($e);
		};

		this.populate = function(schedules) {
			var _table_data = [];
			$.each(schedules || [], function(index, schedule) {
				var _date = new Date(schedule.date.date);
				_table_data.push(
					[
						'<input type="checkbox" name="check-schedule[]" value="' + schedule.id + '" />',
						schedule.id,
						schedule.term.name,
						_date.getDate() + '/' + (_date.getMonth()+1) + '/' + _date.getFullYear(),
						schedule.subject.title,
						'<button class="button delete-schedule" data-schedule-id="' + schedule.id + '">Delete</button>'
					]
				);
			});
			_schedule_table.rows.add(_table_data).draw();
			_setTableListeners(_$schedule_tab.find('#schedule-table'));
		};

		this.viewTable = function() {
			_$schedule_tab.find('#add-schedule-container').hide();
			_$schedule_tab.find('.header, #schedule-table-container').fadeIn(300);
		};

		this.clearTable = function() {
			_schedule_table.clear().draw();
		};

		function _setListeners($e) {
			$e.find('.add-schedule').on('click', function() {
				$e.find('.header, #schedule-table-container').hide();
				$e.find('#add-schedule-form').trigger('reset');
				$e.find('#add-schedule-container').fadeIn(300);
			});

			$e.find('.cancel').on('click', function(e) {
				e.preventDefault();
				$e.find('#add-schedule-container').hide();
				$e.find('.header, #schedule-table-container').fadeIn(300);
			});

			$e.find('[name="term_id"]').on('change', function() {
				var _$self = $(this);
				$e.find('[name="date"]').val('');
				if ($(this).val()) {
					var _$selected_option = _$self.find('option:selected');
					var _start_date = _$selected_option.attr('data-start-date');
					var _end_date = _$selected_option.attr('data-end-date');
					$e.find('#add-schedule-form [name="date"]').datepicker('option', 'minDate', _start_date);
					$e.find('#add-schedule-form [name="date"]').datepicker('option', 'maxDate', _end_date);
				}
			});
		}

		function _setTableListeners($e) {
			$e.find('.delete-schedule').on('click', function() {
				var _schedule_id = $(this).attr('data-schedule-id');
				_me.broadcast('delete_schedule', _schedule_id);
			});
		}
	}
});
<commit_msg>Make the term and the subject as link<commit_after>define([
	'jquery',
	'Global/SQ',
	'SQ/Broadcaster',
	'SQ/Util',
	'underscore',
	'text!./template/schedule_table.tmpl',
	'datatables',
	'jqueryui'
], function(
	$,
	SQ,
	Broadcaster,
	Util,
	_,
	ScheduleTableTemplate
) {
	'use strict';

	return function ScheduleTab() {

		var _me = this;
		var _util = new Util();
		var _$schedule_tab = null;
		var _schedule_table = null;
		var _add_schedule_data = {};

		SQ.mixin(_me, new Broadcaster(['add_schedule', 'delete_schedule']));

		(function _init() {
		})();

		this.initialize = function($e) {
			_$schedule_tab = $e;
			$e.find('#add-schedule-form [name="date"]').datepicker({
				dateFormat: 'yy-mm-dd'
			});

			_$schedule_tab.find('#schedule-table-container').append(_.template(ScheduleTableTemplate));
			_schedule_table = _$schedule_tab.find('#schedule-table').DataTable({
				bProcessing: true,
				fnDrawCallback: function(oSettings) {
				}
			});

			_$schedule_tab.find('#add-schedule-form').validate({
				rules: {
					term_id: {
						required: true
					},
					subject_id: {
						required: true
					},
					date: {
						required: true,
						remote: {
							url: '/ajax/schedule/dateNotExist',
							type: 'post',
							data: {classroom_id: $e.find('[name="classroom_id"]').val()}
						}
					}
				},
				messages: {
					date: {
						remote: 'There is already a schedule exists on this date'
					}
				},
				submitHandler: function(form) {
					_add_schedule_data = _util.serializeJSON($(form));
					_me.broadcast('add_schedule', _add_schedule_data);
				}
			});

			_setListeners($e);
		};

		this.populate = function(schedules) {
			var _table_data = [];
			$.each(schedules || [], function(index, schedule) {
				var _date = new Date(schedule.date.date);
				_table_data.push(
					[
						'<input type="checkbox" name="check-schedule[]" value="' + schedule.id + '" />',
						schedule.id,
						'' + schedule.term.name + '',
						_date.getDate() + '/' + (_date.getMonth()+1) + '/' + _date.getFullYear(),
						'' + schedule.subject.title + '',
						'<button class="button delete-schedule" data-schedule-id="' + schedule.id + '">Delete</button>'
					]
				);
			});
			_schedule_table.rows.add(_table_data).draw();
			_setTableListeners(_$schedule_tab.find('#schedule-table'));
		};

		this.viewTable = function() {
			_$schedule_tab.find('#add-schedule-container').hide();
			_$schedule_tab.find('.header, #schedule-table-container').fadeIn(300);
		};

		this.clearTable = function() {
			_schedule_table.clear().draw();
		};

		function _setListeners($e) {
			$e.find('.add-schedule').on('click', function() {
				$e.find('.header, #schedule-table-container').hide();
				$e.find('#add-schedule-form').trigger('reset');
				$e.find('#add-schedule-container').fadeIn(300);
			});

			$e.find('.cancel').on('click', function(e) {
				e.preventDefault();
				$e.find('#add-schedule-container').hide();
				$e.find('.header, #schedule-table-container').fadeIn(300);
			});

			$e.find('[name="term_id"]').on('change', function() {
				var _$self = $(this);
				$e.find('[name="date"]').val('');
				if ($(this).val()) {
					var _$selected_option = _$self.find('option:selected');
					var _start_date = _$selected_option.attr('data-start-date');
					var _end_date = _$selected_option.attr('data-end-date');
					$e.find('#add-schedule-form [name="date"]').datepicker('option', 'minDate', _start_date);
					$e.find('#add-schedule-form [name="date"]').datepicker('option', 'maxDate', _end_date);
				}
			});
		}

		function _setTableListeners($e) {
			$e.find('.delete-schedule').on('click', function() {
				var _schedule_id = $(this).attr('data-schedule-id');
				_me.broadcast('delete_schedule', _schedule_id);
			});
		}
	}
});
<|endoftext|>


<commit_before>import React from "react";
import { connect } from "react-redux";
import { getCurrentTrackDisplayName } from "../../selectors";
import DropTarget from "../DropTarget";
import PresetOverlay from "./PresetOverlay";

const USER_PRESET_TRANSITION_SECONDS = 5.7;
const PRESET_TRANSITION_SECONDS = 2.7;
const MILLISECONDS_BETWEEN_PRESET_TRANSITIONS = 15000;

class Milkdrop extends React.Component {
  constructor(props) {
    super(props);
    this.__debugState = "CONSTRUCTED";
    this.__updates = 0;
    this.state = {
      isFullscreen: false,
      presetOverlay: false
    };
  }

  async componentDidMount() {
    this.__debugState = "MOUNT_STARTED";
    this.visualizer = this.props.butterchurn.createVisualizer(
      this.props.analyser.context,
      this._canvasNode,
      {
        width: this.props.width,
        height: this.props.height,
        meshWidth: 32,
        meshHeight: 24,
        pixelRatio: window.devicePixelRatio || 1
      }
    );
    this.visualizer.connectAudio(this.props.analyser);
    this.presetCycle = !this.props.initialPreset;
    if (this.props.initialPreset) {
      const presetIndices = this.props.presets.addPresets(
        this.props.initialPreset
      );
      this.selectPreset(
        await this.props.presets.selectIndex(presetIndices[0]),
        0
      );
    } else {
      this.selectPreset(this.props.presets.getCurrent(), 0);
    }

    // Kick off the animation loop
    const loop = () => {
      if (this.props.playing && this.props.isEnabledVisualizer) {
        this.visualizer.render();
      }
      this._animationFrameRequest = window.requestAnimationFrame(loop);
    };
    loop();

    this._unsubscribeFocusedKeyDown = this.props.onFocusedKeyDown(
      this._handleFocusedKeyboardInput
    );
    this.__debugState = "MOUNT_ENDED";
  }

  componentWillUnmount() {
    this.__debugState = "WILL_UNMOUNT";
    this._pauseViz();
    this._stopCycling();
    if (this._unsubscribeFocusedKeyDown) {
      this._unsubscribeFocusedKeyDown();
    }
  }

  componentDidUpdate(prevProps) {
    this.__updates++;
    if (this.visualizer == null) {
      // https://github.com/captbaritone/webamp/issues/731
      throw new Error(
        `Weird bug: State=${this.__debugState} updates=${this.__updates}`
      );
    }
    if (
      this.props.width !== prevProps.width ||
      this.props.height !== prevProps.height
    ) {
      this.visualizer.setRendererSize(this.props.width, this.props.height);
    }

    if (this.props.trackTitle !== prevProps.trackTitle) {
      this.visualizer.launchSongTitleAnim(this.props.trackTitle);
    }
  }

  _pauseViz() {
    if (this._animationFrameRequest) {
      window.cancelAnimationFrame(this._animationFrameRequest);
      this._animationFrameRequest = null;
    }
  }

  _stopCycling() {
    if (this.cycleInterval) {
      clearInterval(this.cycleInterval);
      this.cycleInterval = null;
    }
  }

  _restartCycling() {
    this._stopCycling();

    if (this.presetCycle) {
      this.cycleInterval = setInterval(() => {
        this._nextPreset(PRESET_TRANSITION_SECONDS);
      }, MILLISECONDS_BETWEEN_PRESET_TRANSITIONS);
    }
  }

  _handleFocusedKeyboardInput = e => {
    switch (e.keyCode) {
      case 32: // spacebar
        this._nextPreset(USER_PRESET_TRANSITION_SECONDS);
        break;
      case 8: // backspace
        this._prevPreset(0);
        break;
      case 72: // H
        this._nextPreset(0);
        break;
      case 82: // R
        this.props.presets.toggleRandomize();
        break;
      case 76: // L
        this.setState({ presetOverlay: !this.state.presetOverlay });
        e.stopPropagation();
        break;
      case 84: // T
        this.visualizer.launchSongTitleAnim(this.props.trackTitle);
        e.stopPropagation();
        break;
      case 145: // scroll lock
      case 125: // F14 (scroll lock for OS X)
        this.presetCycle = !this.presetCycle;
        this._restartCycling();
        break;
    }
  };

  _addNewPresets(files) {
    const presetKeys = this.props.presets.getKeys();
    const presets = {};
    const presetIndices = [];
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      const fileName = file.name;
      const presetName = fileName.substring(fileName, fileName.length - 5); // remove .milk
      const presetIdx = presetKeys.indexOf(presetName);
      if (presetIdx >= 0) {
        presetIndices.push(presetIdx);
      } else {
        presets[presetName] = { file };
      }
    }

    if (Object.keys(presets).length > 0) {
      const filePresetIndices = this.props.presets.addPresets(presets);
      for (let j = filePresetIndices[0]; j < filePresetIndices[1]; j++) {
        presetIndices.push(j);
      }
    }

    return presetIndices;
  }

  async _handleDrop(e) {
    const files = e.dataTransfer.files;
    if (files.length > 0) {
      const milkFiles = Array.from(files).filter(file =>
        file.name.endsWith(".milk")
      );
      if (milkFiles.length === 0) {
        alert("Visualizer only supports .milk files");
        return;
      }

      const presetIndices = this._addNewPresets(milkFiles);
      this.selectPreset(
        await this.props.presets.selectIndex(presetIndices[0]),
        PRESET_TRANSITION_SECONDS
      );
    }
  }

  async _nextPreset(blendTime) {
    this.selectPreset(await this.props.presets.next(), blendTime);
  }

  async _prevPreset(blendTime) {
    this.selectPreset(await this.props.presets.previous(), blendTime);
  }

  selectPreset(preset, blendTime = 0) {
    if (preset != null) {
      this.visualizer.loadPreset(preset, blendTime);
      this._restartCycling();
      // TODO: Kinda weird that we use the passed preset for the visualizer,
      // but set state to the current. Maybe we should just always use the curent..
      this.setState({ currentPreset: this.props.presets.getCurrentIndex() });
    }
  }

  async loadPresets(presetFiles) {
    const presets = {};
    const milkFiles = Array.from(presetFiles).filter(file =>
      file.name.endsWith(".milk")
    );
    for (let i = 0; i < milkFiles.length; i++) {
      const file = milkFiles[i];
      presets[file.name] = { file };
    }
    const numPresets = this.props.presets.loadPresets(presets);
    this.selectPreset(
      await this.props.presets.selectIndex(
        Math.floor(Math.random() * numPresets)
      ),
      PRESET_TRANSITION_SECONDS
    );
  }

  closePresetOverlay() {
    this.setState({ presetOverlay: false });
  }

  render() {
    return (
      <DropTarget id="milkdrop-window" handleDrop={e => this._handleDrop(e)}>
        {this.state.presetOverlay && (
          <PresetOverlay
            width={this.props.width}
            height={this.props.height}
            presetKeys={this.props.presets.getKeys()}
            currentPreset={this.state.currentPreset}
            onFocusedKeyDown={listener => this.props.onFocusedKeyDown(listener)}
            selectPreset={async idx => {
              this.selectPreset(await this.props.presets.selectIndex(idx), 0);
            }}
            loadPresets={async presetFiles => this.loadPresets(presetFiles)}
            closeOverlay={() => this.closePresetOverlay()}
          />
        )}
        <canvas
          height={this.props.height}
          width={this.props.width}
          style={{
            height: "100%",
            width: "100%",
            display: this.props.isEnabledVisualizer ? "block" : "none"
          }}
          ref={node => (this._canvasNode = node)}
        />
      </DropTarget>
    );
  }
}

const mapStateToProps = state => ({
  trackTitle: getCurrentTrackDisplayName(state)
});

export default connect(mapStateToProps)(Milkdrop);
<commit_msg>More logging to help figure out #731<commit_after>import React from "react";
import { connect } from "react-redux";
import { getCurrentTrackDisplayName } from "../../selectors";
import DropTarget from "../DropTarget";
import PresetOverlay from "./PresetOverlay";

const USER_PRESET_TRANSITION_SECONDS = 5.7;
const PRESET_TRANSITION_SECONDS = 2.7;
const MILLISECONDS_BETWEEN_PRESET_TRANSITIONS = 15000;

class Milkdrop extends React.Component {
  constructor(props) {
    super(props);
    this.__debugState = "CONSTRUCTED";
    this.__updates = 0;
    this.state = {
      isFullscreen: false,
      presetOverlay: false
    };
  }

  async componentDidMount() {
    this.__debugState = "MOUNT_STARTED";
    this.visualizer = this.props.butterchurn.createVisualizer(
      this.props.analyser.context,
      this._canvasNode,
      {
        width: this.props.width,
        height: this.props.height,
        meshWidth: 32,
        meshHeight: 24,
        pixelRatio: window.devicePixelRatio || 1
      }
    );
    if (this.visualizer == null) {
      // https://github.com/captbaritone/webamp/issues/731
      throw new Error("Visualizer not initialized. WAT.");
    }
    this.visualizer.connectAudio(this.props.analyser);
    this.presetCycle = !this.props.initialPreset;
    if (this.props.initialPreset) {
      const presetIndices = this.props.presets.addPresets(
        this.props.initialPreset
      );
      this.selectPreset(
        await this.props.presets.selectIndex(presetIndices[0]),
        0
      );
    } else {
      this.selectPreset(this.props.presets.getCurrent(), 0);
    }

    // Kick off the animation loop
    const loop = () => {
      if (this.props.playing && this.props.isEnabledVisualizer) {
        this.visualizer.render();
      }
      this._animationFrameRequest = window.requestAnimationFrame(loop);
    };
    loop();

    this._unsubscribeFocusedKeyDown = this.props.onFocusedKeyDown(
      this._handleFocusedKeyboardInput
    );
    if (this.visualizer == null) {
      // https://github.com/captbaritone/webamp/issues/731
      throw new Error("Visualizer not initialized after await. WAT.");
    }
    this.__debugState = "MOUNT_ENDED";
  }

  componentWillUnmount() {
    this.__debugState = "WILL_UNMOUNT";
    this._pauseViz();
    this._stopCycling();
    if (this._unsubscribeFocusedKeyDown) {
      this._unsubscribeFocusedKeyDown();
    }
  }

  componentDidUpdate(prevProps) {
    this.__updates++;
    if (this.visualizer == null) {
      // https://github.com/captbaritone/webamp/issues/731
      throw new Error(
        `Weird bug: State=${this.__debugState} updates=${this.__updates}`
      );
    }
    if (
      this.props.width !== prevProps.width ||
      this.props.height !== prevProps.height
    ) {
      this.visualizer.setRendererSize(this.props.width, this.props.height);
    }

    if (this.props.trackTitle !== prevProps.trackTitle) {
      this.visualizer.launchSongTitleAnim(this.props.trackTitle);
    }
  }

  _pauseViz() {
    if (this._animationFrameRequest) {
      window.cancelAnimationFrame(this._animationFrameRequest);
      this._animationFrameRequest = null;
    }
  }

  _stopCycling() {
    if (this.cycleInterval) {
      clearInterval(this.cycleInterval);
      this.cycleInterval = null;
    }
  }

  _restartCycling() {
    this._stopCycling();

    if (this.presetCycle) {
      this.cycleInterval = setInterval(() => {
        this._nextPreset(PRESET_TRANSITION_SECONDS);
      }, MILLISECONDS_BETWEEN_PRESET_TRANSITIONS);
    }
  }

  _handleFocusedKeyboardInput = e => {
    switch (e.keyCode) {
      case 32: // spacebar
        this._nextPreset(USER_PRESET_TRANSITION_SECONDS);
        break;
      case 8: // backspace
        this._prevPreset(0);
        break;
      case 72: // H
        this._nextPreset(0);
        break;
      case 82: // R
        this.props.presets.toggleRandomize();
        break;
      case 76: // L
        this.setState({ presetOverlay: !this.state.presetOverlay });
        e.stopPropagation();
        break;
      case 84: // T
        this.visualizer.launchSongTitleAnim(this.props.trackTitle);
        e.stopPropagation();
        break;
      case 145: // scroll lock
      case 125: // F14 (scroll lock for OS X)
        this.presetCycle = !this.presetCycle;
        this._restartCycling();
        break;
    }
  };

  _addNewPresets(files) {
    const presetKeys = this.props.presets.getKeys();
    const presets = {};
    const presetIndices = [];
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      const fileName = file.name;
      const presetName = fileName.substring(fileName, fileName.length - 5); // remove .milk
      const presetIdx = presetKeys.indexOf(presetName);
      if (presetIdx >= 0) {
        presetIndices.push(presetIdx);
      } else {
        presets[presetName] = { file };
      }
    }

    if (Object.keys(presets).length > 0) {
      const filePresetIndices = this.props.presets.addPresets(presets);
      for (let j = filePresetIndices[0]; j < filePresetIndices[1]; j++) {
        presetIndices.push(j);
      }
    }

    return presetIndices;
  }

  async _handleDrop(e) {
    const files = e.dataTransfer.files;
    if (files.length > 0) {
      const milkFiles = Array.from(files).filter(file =>
        file.name.endsWith(".milk")
      );
      if (milkFiles.length === 0) {
        alert("Visualizer only supports .milk files");
        return;
      }

      const presetIndices = this._addNewPresets(milkFiles);
      this.selectPreset(
        await this.props.presets.selectIndex(presetIndices[0]),
        PRESET_TRANSITION_SECONDS
      );
    }
  }

  async _nextPreset(blendTime) {
    this.selectPreset(await this.props.presets.next(), blendTime);
  }

  async _prevPreset(blendTime) {
    this.selectPreset(await this.props.presets.previous(), blendTime);
  }

  selectPreset(preset, blendTime = 0) {
    if (preset != null) {
      this.visualizer.loadPreset(preset, blendTime);
      this._restartCycling();
      // TODO: Kinda weird that we use the passed preset for the visualizer,
      // but set state to the current. Maybe we should just always use the curent..
      this.setState({ currentPreset: this.props.presets.getCurrentIndex() });
    }
  }

  async loadPresets(presetFiles) {
    const presets = {};
    const milkFiles = Array.from(presetFiles).filter(file =>
      file.name.endsWith(".milk")
    );
    for (let i = 0; i < milkFiles.length; i++) {
      const file = milkFiles[i];
      presets[file.name] = { file };
    }
    const numPresets = this.props.presets.loadPresets(presets);
    this.selectPreset(
      await this.props.presets.selectIndex(
        Math.floor(Math.random() * numPresets)
      ),
      PRESET_TRANSITION_SECONDS
    );
  }

  closePresetOverlay() {
    this.setState({ presetOverlay: false });
  }

  render() {
    return (
      <DropTarget id="milkdrop-window" handleDrop={e => this._handleDrop(e)}>
        {this.state.presetOverlay && (
          <PresetOverlay
            width={this.props.width}
            height={this.props.height}
            presetKeys={this.props.presets.getKeys()}
            currentPreset={this.state.currentPreset}
            onFocusedKeyDown={listener => this.props.onFocusedKeyDown(listener)}
            selectPreset={async idx => {
              this.selectPreset(await this.props.presets.selectIndex(idx), 0);
            }}
            loadPresets={async presetFiles => this.loadPresets(presetFiles)}
            closeOverlay={() => this.closePresetOverlay()}
          />
        )}
        <canvas
          height={this.props.height}
          width={this.props.width}
          style={{
            height: "100%",
            width: "100%",
            display: this.props.isEnabledVisualizer ? "block" : "none"
          }}
          ref={node => (this._canvasNode = node)}
        />
      </DropTarget>
    );
  }
}

const mapStateToProps = state => ({
  trackTitle: getCurrentTrackDisplayName(state)
});

export default connect(mapStateToProps)(Milkdrop);
<|endoftext|>


<commit_before>const common = require('../../lib/common');
const themeService = require('../../../frontend/services/themes');
const models = require('../../models');

module.exports = {
    docName: 'themes',

    browse: {
        permissions: true,
        query() {
            return themeService.getJSON();
        }
    },

    activate: {
        headers: {
            cacheInvalidate: true
        },
        options: [
            'name'
        ],
        validation: {
            options: {
                name: {
                    required: true
                }
            }
        },
        permissions: true,
        query(frame) {
            let themeName = frame.options.name;
            const newSettings = [{
                key: 'active_theme',
                value: themeName
            }];

            return themeService.activate(themeName)
                .then((checkedTheme) => {
                    // @NOTE: we use the model, not the API here, as we don't want to trigger permissions
                    return models.Settings.edit(newSettings, frame.options)
                        .then(() => checkedTheme);
                })
                .then((checkedTheme) => {
                    return themeService.getJSON(themeName, checkedTheme);
                });
        }
    },

    upload: {
        headers: {},
        permissions: {
            method: 'add'
        },
        query(frame) {
            // @NOTE: consistent filename uploads
            frame.options.originalname = frame.file.originalname.toLowerCase();

            let zip = {
                path: frame.file.path,
                name: frame.file.originalname
            };

            return themeService.storage.setFromZip(zip)
                .then(({theme, themeOverriden}) => {
                    if (themeOverriden) {
                        // CASE: clear cache
                        this.headers.cacheInvalidate = true;
                    }
                    common.events.emit('theme.uploaded');
                    return theme;
                });
        }
    },

    download: {
        options: [
            'name'
        ],
        validation: {
            options: {
                name: {
                    required: true
                }
            }
        },
        permissions: {
            method: 'read'
        },
        query(frame) {
            let themeName = frame.options.name;

            return themeService.storage.getZip(themeName);
        }
    },

    destroy: {
        statusCode: 204,
        headers: {
            cacheInvalidate: true
        },
        options: [
            'name'
        ],
        validation: {
            options: {
                name: {
                    required: true
                }
            }
        },
        permissions: true,
        query(frame) {
            let themeName = frame.options.name;

            return themeService.storage.destroy(themeName);
        }
    }
};
<commit_msg>🐛 Fixed cache invalidation header on theme override<commit_after>const common = require('../../lib/common');
const themeService = require('../../../frontend/services/themes');
const models = require('../../models');

module.exports = {
    docName: 'themes',

    browse: {
        permissions: true,
        query() {
            return themeService.getJSON();
        }
    },

    activate: {
        headers: {
            cacheInvalidate: true
        },
        options: [
            'name'
        ],
        validation: {
            options: {
                name: {
                    required: true
                }
            }
        },
        permissions: true,
        query(frame) {
            let themeName = frame.options.name;
            const newSettings = [{
                key: 'active_theme',
                value: themeName
            }];

            return themeService.activate(themeName)
                .then((checkedTheme) => {
                    // @NOTE: we use the model, not the API here, as we don't want to trigger permissions
                    return models.Settings.edit(newSettings, frame.options)
                        .then(() => checkedTheme);
                })
                .then((checkedTheme) => {
                    return themeService.getJSON(themeName, checkedTheme);
                });
        }
    },

    upload: {
        headers: {},
        permissions: {
            method: 'add'
        },
        query(frame) {
            // @NOTE: consistent filename uploads
            frame.options.originalname = frame.file.originalname.toLowerCase();

            let zip = {
                path: frame.file.path,
                name: frame.file.originalname
            };

            return themeService.storage.setFromZip(zip)
                .then(({theme, themeOverridden}) => {
                    if (themeOverridden) {
                        // CASE: clear cache
                        this.headers.cacheInvalidate = true;
                    }
                    common.events.emit('theme.uploaded');
                    return theme;
                });
        }
    },

    download: {
        options: [
            'name'
        ],
        validation: {
            options: {
                name: {
                    required: true
                }
            }
        },
        permissions: {
            method: 'read'
        },
        query(frame) {
            let themeName = frame.options.name;

            return themeService.storage.getZip(themeName);
        }
    },

    destroy: {
        statusCode: 204,
        headers: {
            cacheInvalidate: true
        },
        options: [
            'name'
        ],
        validation: {
            options: {
                name: {
                    required: true
                }
            }
        },
        permissions: true,
        query(frame) {
            let themeName = frame.options.name;

            return themeService.storage.destroy(themeName);
        }
    }
};
<|endoftext|>


<commit_before>// Copyright (c) 2015 Analog.IO group. All rights reserved.
// Use of this source code is governed by a Apache-style license that can be
// found in the LICENSE file.
var endpoints = {
  shifts: "https://analogio.dk/publicshiftplanning/api/shifts/analog",
  open: "https://analogio.dk/publicshiftplanning/api/open/analog"
};

function toDate(dateString) { var b = dateString.split(/\D/); return new Date(b[0], b[1]-1, b[2], b[3], b[4], b[5]); }
function addZero(i) { if (i < 10) { i = "0" + i; } return i; }
function dateToString(date) { return date.getHours() + ':' + addZero(date.getMinutes()); }
function dayToString(date) { return date.toDateString(); }
function openingToString(opening) { return dateToString(toDate(opening.start)) + ' - ' + dateToString(toDate(opening.end)); }
function isToday(opening) { var date = toDate(opening.start); var today = new Date(); return date.getDay() == today.getDay() && date.getDate() == today.getDate(); }
function isNow(opening) { var now = new Date(); return toDate(opening.start) <= now && now <= toDate(opening.end); }

function getIsAnalogOpen(callback, errorCallback) {
  var x = new XMLHttpRequest();
  x.open('GET', endpoints.open);
  x.responseType = 'json';
  x.onload = function() {
    var response = x.response;
    if (!response ) {
        errorCallback('No response from CafeAnalog.dk!');
    }
    else
    {
      var isOpen = response.open;
      callback(isOpen);
    }
  };
  x.onerror = function(){ errorCallback('Network error.'); };
  x.send();
}

function getOpenings(success, error) {
  getShowOpening(function (showOpening) {
    if (!showOpening) return;

    var x = new XMLHttpRequest();
    x.open('GET', endpoints.shifts);
    x.responseType = 'json';
    x.onload = function() {
      var openings = x.response;
      if (!openings) {
        error();
      }
      else
      {
        success(openings);
      }
    }
    x.onerror = function(){};
    x.send();
  });
}

function getCurrentOpening(success, error) {
  getOpenings(function (openings) {
    var opening = openings.find(isNow);

    if (opening) success(opening);
    else error();
  });
}

function getOpening(callback, errorCallback) {
  getShowOpening( function (showOpening) { // Check the settings to see if the opening should be downloaded or not.
    if (!showOpening) return;

    getOpenings(function(openings) {
	  var now = new Date();
      var currentIndex = openings.findIndex(isNow);

      if (currentIndex === -1) {
        var afterNow = openings.filter(function(value) { return now <= toDate(value.start); });

        if (afterNow.length == 0) {
          errorCallback('No openings found');
        } else {
          var d = "2017-03-28T08:00:00";
          if (isToday(afterNow[0]))	 {
            errorCallback('We open again today at ' + dateToString(toDate(afterNow[0].start)));
          } else {
            var date = toDate(afterNow[0].start);
            errorCallback('We open again ' + dayToString(date) + ' at ' + dateToString(date));
          }
        }

        return;
      }

      var opening = openings[currentIndex];

      if (isToday(opening)) {
        var nextOpening = openings[currentIndex + 1];
        var openingString = 'today: ' + openingToString(opening);
        if (nextOpening && isToday(nextOpening)) {
          callback(openingString + " and " + openingToString(nextOpening));
        } else {
          callback(openingString); // Check that this works for all dates.
        }
      } else {
        callback(openingToString(opening));
      }
    }, function() {
      errorCallback('No openings found');
    });
  });
}

function getNames(callback, errorCallback) {
  getShowNames( function (showNames) {
    if (!showNames) return;
    getCurrentOpening(function (opening) {
      var checkedIn = opening.checkedInEmployees;

      var joined = checkedIn.map(function(emp) { return emp.firstName; }).join(', ');
      var lastAnd = joined.lastIndexOf(',');

      callback(joined.substring(0, lastAnd) + ' and' + joined.substring(lastAnd + 1));
    }, function () { errorCallback('No names found'); });
  });
}

function renderOpening(openingText) { document.getElementById('opening').textContent = openingText; }
function renderNames(namesText) { document.getElementById('onshift').innerHTML = namesText; }
function renderStatus(statusText) { document.getElementById('status').textContent = statusText; }

function boolToText(isOpenBool) {
  if (isOpenBool)
  {
    setIcon("Open");
    return "ÅPEN";
  }
  else
  {
    setIcon("");
    return "CLØSED";
  }
}

function setIcon(openString) { chrome.browserAction.setIcon({path:"Assets/icon"+openString+"38.png"}); }

function getShowNames(callback) {
  chrome.storage.sync.get({
    showshiftsetting: true
  }, function(items) {
    callback(items.showshiftsetting);
  })
}

function getShowOpening(callback) {
  chrome.storage.sync.get({
    showopeningsetting: true
  }, function(items) {
    callback(items.showopeningsetting);
  })
}

document.addEventListener('DOMContentLoaded', function() {
  renderStatus('Cafe Analog is ...');

  getIsAnalogOpen( function (boolValue) {
    renderStatus('Cafe Analog is ' + boolToText(boolValue));
    // names
    if (boolValue) {
      getNames( function (names) {
        renderNames(names); // put special handeling if necesarry
      }, renderNames);
    }
    // opening
    getOpening( function(openingText) {
      if (boolValue)
      {
        openingText = openingText.charAt(0).toUpperCase() + openingText.slice(1);;
        renderOpening(openingText);
      }
      else
      {
        renderOpening("We open again " + openingText);
      }
    }, renderOpening);
  }, renderStatus);
});
<commit_msg>Don't write "and" when only one barista is on shift.<commit_after>// Copyright (c) 2015 Analog.IO group. All rights reserved.
// Use of this source code is governed by a Apache-style license that can be
// found in the LICENSE file.
var endpoints = {
  shifts: "https://analogio.dk/publicshiftplanning/api/shifts/analog",
  open: "https://analogio.dk/publicshiftplanning/api/open/analog"
};

function toDate(dateString) { var b = dateString.split(/\D/); return new Date(b[0], b[1]-1, b[2], b[3], b[4], b[5]); }
function addZero(i) { if (i < 10) { i = "0" + i; } return i; }
function dateToString(date) { return date.getHours() + ':' + addZero(date.getMinutes()); }
function dayToString(date) { return date.toDateString(); }
function openingToString(opening) { return dateToString(toDate(opening.start)) + ' - ' + dateToString(toDate(opening.end)); }
function isToday(opening) { var date = toDate(opening.start); var today = new Date(); return date.getDay() == today.getDay() && date.getDate() == today.getDate(); }
function isNow(opening) { var now = new Date(); return toDate(opening.start) <= now && now <= toDate(opening.end); }

function getIsAnalogOpen(callback, errorCallback) {
  var x = new XMLHttpRequest();
  x.open('GET', endpoints.open);
  x.responseType = 'json';
  x.onload = function() {
    var response = x.response;
    if (!response ) {
        errorCallback('No response from CafeAnalog.dk!');
    }
    else
    {
      var isOpen = response.open;
      callback(isOpen);
    }
  };
  x.onerror = function(){ errorCallback('Network error.'); };
  x.send();
}

function getOpenings(success, error) {
  getShowOpening(function (showOpening) {
    if (!showOpening) return;

    var x = new XMLHttpRequest();
    x.open('GET', endpoints.shifts);
    x.responseType = 'json';
    x.onload = function() {
      var openings = x.response;
      if (!openings) {
        error();
      }
      else
      {
        success(openings);
      }
    }
    x.onerror = function(){};
    x.send();
  });
}

function getCurrentOpening(success, error) {
  getOpenings(function (openings) {
    var opening = openings.find(isNow);

    if (opening) success(opening);
    else error();
  });
}

function getOpening(callback, errorCallback) {
  getShowOpening( function (showOpening) { // Check the settings to see if the opening should be downloaded or not.
    if (!showOpening) return;

    getOpenings(function(openings) {
	  var now = new Date();
      var currentIndex = openings.findIndex(isNow);

      if (currentIndex === -1) {
        var afterNow = openings.filter(function(value) { return now <= toDate(value.start); });

        if (afterNow.length == 0) {
          errorCallback('No openings found');
        } else {
          var d = "2017-03-28T08:00:00";
          if (isToday(afterNow[0]))	 {
            errorCallback('We open again today at ' + dateToString(toDate(afterNow[0].start)));
          } else {
            var date = toDate(afterNow[0].start);
            errorCallback('We open again ' + dayToString(date) + ' at ' + dateToString(date));
          }
        }

        return;
      }

      var opening = openings[currentIndex];

      if (isToday(opening)) {
        var nextOpening = openings[currentIndex + 1];
        var openingString = 'today: ' + openingToString(opening);
        if (nextOpening && isToday(nextOpening)) {
          callback(openingString + " and " + openingToString(nextOpening));
        } else {
          callback(openingString); // Check that this works for all dates.
        }
      } else {
        callback(openingToString(opening));
      }
    }, function() {
      errorCallback('No openings found');
    });
  });
}

function getNames(callback, errorCallback) {
  getShowNames( function (showNames) {
    if (!showNames) return;
    getCurrentOpening(function (opening) {
      var checkedIn = opening.checkedInEmployees;

      var joined = checkedIn.map(function(emp) { return emp.firstName; }).join(', ');

      var lastAnd = joined.lastIndexOf(',');
      if (lastAnd > -1) {
        callback(joined.substring(0, lastAnd) + ' and' + joined.substring(lastAnd + 1));
      } else {
        callback(joined)
      }
    }, function () { errorCallback('No names found'); });
  });
}

function renderOpening(openingText) { document.getElementById('opening').textContent = openingText; }
function renderNames(namesText) { document.getElementById('onshift').innerHTML = namesText; }
function renderStatus(statusText) { document.getElementById('status').textContent = statusText; }

function boolToText(isOpenBool) {
  if (isOpenBool)
  {
    setIcon("Open");
    return "ÅPEN";
  }
  else
  {
    setIcon("");
    return "CLØSED";
  }
}

function setIcon(openString) { chrome.browserAction.setIcon({path:"Assets/icon"+openString+"38.png"}); }

function getShowNames(callback) {
  chrome.storage.sync.get({
    showshiftsetting: true
  }, function(items) {
    callback(items.showshiftsetting);
  })
}

function getShowOpening(callback) {
  chrome.storage.sync.get({
    showopeningsetting: true
  }, function(items) {
    callback(items.showopeningsetting);
  })
}

document.addEventListener('DOMContentLoaded', function() {
  renderStatus('Cafe Analog is ...');

  getIsAnalogOpen( function (boolValue) {
    renderStatus('Cafe Analog is ' + boolToText(boolValue));
    // names
    if (boolValue) {
      getNames( function (names) {
        renderNames(names); // put special handeling if necesarry
      }, renderNames);
    }
    // opening
    getOpening( function(openingText) {
      if (boolValue)
      {
        openingText = openingText.charAt(0).toUpperCase() + openingText.slice(1);;
        renderOpening(openingText);
      }
      else
      {
        renderOpening("We open again " + openingText);
      }
    }, renderOpening);
  }, renderStatus);
});
<|endoftext|>