<commit_before>/*
 * Chat log viewer plugin by jd
 */
'use strict';

const fs = require('fs');
const MAX_LINES = 1000;

exports.commands = {
	viewlogs: function (target, room, user) {
		if (target) {
			let targets = target.split(',');
			for (let u in targets) targets[u] = targets[u].trim();
			if (!targets[1]) return this.errorReply("Please use /viewlogs with no target.");
			switch (toId(targets[0])) {
			case 'month':
				if (!targets[1]) return this.errorReply("Please use /viewlogs with no target.");
				if (!permissionCheck(user, targets[1])) return this.errorReply("/viewlogs - Access denied.");
				let months = fs.readdirSync('logs/chat/' + targets[1]);
				user.send("|popup||html|Choose a month:" + generateTable(months, "/viewlogs date," + targets[1] + ","));
				return;
			case 'date':
				if (!targets[2]) return this.errorReply("Please use /viewlogs with no target.");
				if (!permissionCheck(user, targets[1])) return this.errorReply("/viewlogs - Access denied.");
				let days = fs.readdirSync('logs/chat/' + targets[1] + '/' + targets[2]);
				user.send("|popup||html|Choose a date:" + generateTable(days, "/viewlogspopup " + targets[1] + ","));
				return;
			default:
				this.errorReply("/viewlogs - Command not recognized.");
				break;
			}
		}

		let rooms = fs.readdirSync('logs/chat');
		let roomList = [], groupChats = [];

		for (let u in rooms) {
			if (!rooms[u]) continue;
			if (rooms[u] === 'README.md') continue;
			if (!permissionCheck(user, rooms[u])) continue;
			(rooms[u].includes('groupchat-') ? groupChats : roomList).push(rooms[u]);
		}
		if (roomList.length < 1) return this.errorReply("You don't have access to view the logs of any rooms.");

		let output = "Choose a room to view the logs:";
		output += generateTable(roomList, "/viewlogs month,");
		output += "
Group Chats:" + generateTable(groupChats, "/viewlogs month,");
		user.send("|popup||wide||html|" + output);
	},

	viewlogspopup: 'viewlogs2',
	viewlogs2: function (target, room, user, connection, cmd) {
		if (!target) return this.sendReply("Usage: /viewlogs [room], [year-month-day / 2014-12-08] - Provides you with a temporary link to view the target rooms chat logs.");
		let targetSplit = target.split(',');
		if (!targetSplit[1]) return this.sendReply("Usage: /viewlogs [room], [year-month-day / 2014-12-08] -Provides you with a temporary link to view the target rooms chat logs.");
		for (let u in targetSplit) targetSplit[u] = targetSplit[u].trim();
		let targetRoom = targetSplit[0];
		if (!permissionCheck(user, targetRoom)) return this.errorReply("/viewlogs - Access denied.");
		let date;
		if (toId(targetSplit[1]) === 'today' || toId(targetSplit[1]) === 'yesterday') {
			date = new Date();
			if (toId(targetSplit[1]) === 'yesterday') date.setDate(date.getDate() - 1);
			date = date.toLocaleDateString('en-US', {
				day : 'numeric',
				month : 'numeric',
				year : 'numeric',
			}).split('/').reverse();
			if (date[1] < 10) date[1] = "0" + date[1];
			if (date[2] < 10) date[2] = "0" + date[2];
			targetSplit[1] = date[0] + '-' + date[2] + '-' + date[1];
		}
		date = targetSplit[1].replace(/\.txt/, '');
		let splitDate = date.split('-');
		if (splitDate.length < 3) return this.sendReply("Usage: /viewlogs [room], [year-month-day / 2014-12-08] -Provides you with a temporary link to view the target rooms chat logs.");

		fs.readFile('logs/chat/' + targetRoom.toLowerCase() + '/' + splitDate[0] + '-' + splitDate[1] + '/' + date + '.txt', 'utf8', (err, data) => {
			if (err && err.code === "ENOENT") return user.send("|popup||html|No logs found.");
			if (err) return this.errorReply("/viewlogs - Error: " + err);
			fs.appendFile('logs/viewlogs.log', '[' + new Date().toUTCString() + '] ' + user.name + " viewed the logs of " + toId(targetRoom) + ". Date: " + date + '\n');
			let filename = require('crypto').randomBytes(4).toString('hex');

			if (!user.can('warn', null, Rooms(targetRoom))) {
				let lines = data.split('\n');
				for (let line in lines) {
					if (lines[line].substr(9).trim().charAt(0) === '(') lines.slice(line, 1);
				}
				data = lines.join('\n');
			}

			if (cmd === 'viewlogspopup') {
				let output = 'Displaying room logs of room "' + Tools.escapeHTML(targetRoom) + '" on ' + Tools.escapeHTML(date) + '
';
				data = data.split('\n');
				for (let u in data) {
					if (data[u].length < 1) continue;
					let message = parseMessage(data[u], user.userid);
					if (message.length < 1) continue;
					output += message + '
';
				}
				return user.send("|popup||wide||html|" + output);
			}

			data = targetRoom + "|" + date + "|" + JSON.stringify(Wisp.customColors) + "\n" + data;

			fs.writeFile('static/logs/' + filename, data, err => {
				if (err) return this.errorReply("/viewlogs - " + err);
				this.sendReply(
					"|raw|You can view the logs at http://158.69.196.64:" + Config.port +
					"/logs/logviewer.html?file=" + filename + ""
				);
				setTimeout(function () {
					fs.unlink('static/logs/' + filename);
				}, 1 * 1000 * 60);
			});
		});
	},

	searchlogs: function (target, room, user) {
		if (!target) return this.parse('/help searchlogs');
		let targets = target.split(',');
		for (let u in targets) targets[u] = targets[u].trim();
		if (!targets[1]) return this.errorReply("Please specify a phrase to search.");

		if (toId(targets[0]) === 'all' && !this.can('hotpatch')) return false;
		if (!Rooms(targets[0]) && !this.can('hotpatch') || !this.can('mute', null, Rooms(targets[0]))) return false;

		let pattern = escapeRegExp(targets[1]).replace(/\\\*/g, '.*');
		let command = 'grep -Rnw \'./logs/chat/' + (toId(targets[0]) === 'all' ? '' : toId(targets[0])) + '\' -e "' + pattern + '"';

		require('child_process').exec(command, function (error, stdout, stderr) {
			if (error && stderr) {
				user.popup("/searchlogs doesn't support Windows.");
				console.log("/searchlogs error: " + error);
				return false;
			}
			if (!stdout) return user.popup('Could not find any logs containing "' + pattern + '".');
			let output = '';
			stdout = stdout.split('\n');
			for (let i = 0; i < stdout.length; i++) {
				if (stdout[i].length < 1 || i > MAX_LINES) continue;
				let file = stdout[i].substr(0, stdout[i].indexOf(':'));
				let lineNumber = stdout[i].split(':')[1];
				let line = stdout[i].split(':');
				line.splice(0, 2);
				line = line.join(':');
				let message = parseMessage(line, user.userid);
				if (message.length < 1) continue;
				output += '' + Tools.escapeHTML(file) + ':' + lineNumber +
					':' + message + '
';
			}
			user.send('|popup||wide||html|Displaying last ' + MAX_LINES + ' lines containing "' + Tools.escapeHTML(pattern) + '"' +
				(toId(targets[0]) === 'all' ? '' : ' in "' + Tools.escapeHTML(targets[0]) + '"') + ':

' + output);
		});
	},
	searchlogshelp: ["/searchlogs [room / all], [phrase] - Phrase may contain * wildcards."],
};

function permissionCheck(user, room) {
	if (!Rooms(room) && !user.can('seniorstaff')) {
		return false;
	}
	if (!user.can('lock') && !user.can('warn', null, Rooms(room))) {
		return false;
	}
	if (Rooms(room) && Rooms(room).isPrivate && (!user.can('seniorstaff') && !user.can('warn', null, Rooms(room)))) {
		return false;
	}
	if (Rooms(room) && Rooms(room).isPersonal && (!user.can('seniorstaff') && !user.can('warn', null, Rooms(room)))) {
		return false;
	}
	return true;
}

function generateTable(array, command) {
	let output = "
";
	let count = 0;
	for (let u in array) {
		if (array[u] === 'today.txt') continue;
		if (count === 0) output += "
";
		output += '<button style="width: 100%" name="send" value="' + command + Tools.escapeHTML(array[u]) + '">' +
		(Rooms(array[u]) ? '' : '') + Tools.escapeHTML(array[u]) + (Rooms(array[u]) ? '' : '') + '</button>	';
		count++;
		if (count > 3) {
			output += '
';
			count = 0;
		}
	}
	output += '
';
	return output;
}

function escapeRegExp(s) {
	return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

function parseMessage(message, user) {
	let timestamp = message.substr(0, 9).trim();
	message = message.substr(9).trim();
	let lineSplit = message.split('|');

	switch (lineSplit[1]) {
	case 'c':
		let name = lineSplit[2];
		if (name === '~') break;
		let highlight = new RegExp("\\b" + toId(user) + "\\b", 'gi');
		let div = "chat";
		if (lineSplit.slice(3).join('|').match(highlight)) div = "chat highlighted";
		message = '[' + timestamp + '] ' + '' + name.substr(0, 1) +
		'' + name.substr(1, name.length) + ':' +
		Wisp.parseMessage(lineSplit.slice(3).join('|')) + '';
		break;
	case 'uhtml':
		message = '' + lineSplit.slice(3).join('|').trim() + '';
		break;
	case 'raw':
	case 'html':
		message = '' + lineSplit.slice(2).join('|').trim() + '';
		break;
	case '':
		message = '' + Tools.escapeHTML(lineSplit.slice(1).join('|')) + '';
		break;
	case 'j':
	case 'J':
	case 'l':
	case 'L':
	case 'N':
	case 'unlink':
	case 'userstats':
	case 'tournament':
	case 'uhtmlchange':
		message = "";
		break;
	default:
		message = '' + Tools.escapeHTML(message) + '';
		break;
	}
	return message;
}
<commit_msg>Restrict viewing upperstaff logs to upperstaff...<commit_after>/*
 * Chat log viewer plugin by jd
 */
'use strict';

const fs = require('fs');
const MAX_LINES = 1000;

exports.commands = {
	viewlogs: function (target, room, user) {
		if (target) {
			let targets = target.split(',');
			for (let u in targets) targets[u] = targets[u].trim();
			if (!targets[1]) return this.errorReply("Please use /viewlogs with no target.");
			switch (toId(targets[0])) {
			case 'month':
				if (!targets[1]) return this.errorReply("Please use /viewlogs with no target.");
				if (!permissionCheck(user, targets[1])) return this.errorReply("/viewlogs - Access denied.");
				let months = fs.readdirSync('logs/chat/' + targets[1]);
				user.send("|popup||html|Choose a month:" + generateTable(months, "/viewlogs date," + targets[1] + ","));
				return;
			case 'date':
				if (!targets[2]) return this.errorReply("Please use /viewlogs with no target.");
				if (!permissionCheck(user, targets[1])) return this.errorReply("/viewlogs - Access denied.");
				let days = fs.readdirSync('logs/chat/' + targets[1] + '/' + targets[2]);
				user.send("|popup||html|Choose a date:" + generateTable(days, "/viewlogspopup " + targets[1] + ","));
				return;
			default:
				this.errorReply("/viewlogs - Command not recognized.");
				break;
			}
		}

		let rooms = fs.readdirSync('logs/chat');
		let roomList = [], groupChats = [];

		for (let u in rooms) {
			if (!rooms[u]) continue;
			if (rooms[u] === 'README.md') continue;
			if (!permissionCheck(user, rooms[u])) continue;
			(rooms[u].includes('groupchat-') ? groupChats : roomList).push(rooms[u]);
		}
		if (roomList.length < 1) return this.errorReply("You don't have access to view the logs of any rooms.");

		let output = "Choose a room to view the logs:";
		output += generateTable(roomList, "/viewlogs month,");
		output += "
Group Chats:" + generateTable(groupChats, "/viewlogs month,");
		user.send("|popup||wide||html|" + output);
	},

	viewlogspopup: 'viewlogs2',
	viewlogs2: function (target, room, user, connection, cmd) {
		if (!target) return this.sendReply("Usage: /viewlogs [room], [year-month-day / 2014-12-08] - Provides you with a temporary link to view the target rooms chat logs.");
		let targetSplit = target.split(',');
		if (!targetSplit[1]) return this.sendReply("Usage: /viewlogs [room], [year-month-day / 2014-12-08] -Provides you with a temporary link to view the target rooms chat logs.");
		for (let u in targetSplit) targetSplit[u] = targetSplit[u].trim();
		let targetRoom = targetSplit[0];
		if (!permissionCheck(user, targetRoom)) return this.errorReply("/viewlogs - Access denied.");
		let date;
		if (toId(targetSplit[1]) === 'today' || toId(targetSplit[1]) === 'yesterday') {
			date = new Date();
			if (toId(targetSplit[1]) === 'yesterday') date.setDate(date.getDate() - 1);
			date = date.toLocaleDateString('en-US', {
				day : 'numeric',
				month : 'numeric',
				year : 'numeric',
			}).split('/').reverse();
			if (date[1] < 10) date[1] = "0" + date[1];
			if (date[2] < 10) date[2] = "0" + date[2];
			targetSplit[1] = date[0] + '-' + date[2] + '-' + date[1];
		}
		date = targetSplit[1].replace(/\.txt/, '');
		let splitDate = date.split('-');
		if (splitDate.length < 3) return this.sendReply("Usage: /viewlogs [room], [year-month-day / 2014-12-08] -Provides you with a temporary link to view the target rooms chat logs.");

		fs.readFile('logs/chat/' + targetRoom.toLowerCase() + '/' + splitDate[0] + '-' + splitDate[1] + '/' + date + '.txt', 'utf8', (err, data) => {
			if (err && err.code === "ENOENT") return user.send("|popup||html|No logs found.");
			if (err) return this.errorReply("/viewlogs - Error: " + err);
			fs.appendFile('logs/viewlogs.log', '[' + new Date().toUTCString() + '] ' + user.name + " viewed the logs of " + toId(targetRoom) + ". Date: " + date + '\n');
			let filename = require('crypto').randomBytes(4).toString('hex');

			if (!user.can('warn', null, Rooms(targetRoom))) {
				let lines = data.split('\n');
				for (let line in lines) {
					if (lines[line].substr(9).trim().charAt(0) === '(') lines.slice(line, 1);
				}
				data = lines.join('\n');
			}

			if (cmd === 'viewlogspopup') {
				let output = 'Displaying room logs of room "' + Tools.escapeHTML(targetRoom) + '" on ' + Tools.escapeHTML(date) + '
';
				data = data.split('\n');
				for (let u in data) {
					if (data[u].length < 1) continue;
					let message = parseMessage(data[u], user.userid);
					if (message.length < 1) continue;
					output += message + '
';
				}
				return user.send("|popup||wide||html|" + output);
			}

			data = targetRoom + "|" + date + "|" + JSON.stringify(Wisp.customColors) + "\n" + data;

			fs.writeFile('static/logs/' + filename, data, err => {
				if (err) return this.errorReply("/viewlogs - " + err);
				this.sendReply(
					"|raw|You can view the logs at http://158.69.196.64:" + Config.port +
					"/logs/logviewer.html?file=" + filename + ""
				);
				setTimeout(function () {
					fs.unlink('static/logs/' + filename);
				}, 1 * 1000 * 60);
			});
		});
	},

	searchlogs: function (target, room, user) {
		if (!target) return this.parse('/help searchlogs');
		let targets = target.split(',');
		for (let u in targets) targets[u] = targets[u].trim();
		if (!targets[1]) return this.errorReply("Please specify a phrase to search.");

		if (toId(targets[0]) === 'all' && !this.can('hotpatch')) return false;
		if (!Rooms(targets[0]) && !this.can('hotpatch') || !this.can('mute', null, Rooms(targets[0]))) return false;

		let pattern = escapeRegExp(targets[1]).replace(/\\\*/g, '.*');
		let command = 'grep -Rnw \'./logs/chat/' + (toId(targets[0]) === 'all' ? '' : toId(targets[0])) + '\' -e "' + pattern + '"';

		require('child_process').exec(command, function (error, stdout, stderr) {
			if (error && stderr) {
				user.popup("/searchlogs doesn't support Windows.");
				console.log("/searchlogs error: " + error);
				return false;
			}
			if (!stdout) return user.popup('Could not find any logs containing "' + pattern + '".');
			let output = '';
			stdout = stdout.split('\n');
			for (let i = 0; i < stdout.length; i++) {
				if (stdout[i].length < 1 || i > MAX_LINES) continue;
				let file = stdout[i].substr(0, stdout[i].indexOf(':'));
				let lineNumber = stdout[i].split(':')[1];
				let line = stdout[i].split(':');
				line.splice(0, 2);
				line = line.join(':');
				let message = parseMessage(line, user.userid);
				if (message.length < 1) continue;
				output += '' + Tools.escapeHTML(file) + ':' + lineNumber +
					':' + message + '
';
			}
			user.send('|popup||wide||html|Displaying last ' + MAX_LINES + ' lines containing "' + Tools.escapeHTML(pattern) + '"' +
				(toId(targets[0]) === 'all' ? '' : ' in "' + Tools.escapeHTML(targets[0]) + '"') + ':

' + output);
		});
	},
	searchlogshelp: ["/searchlogs [room / all], [phrase] - Phrase may contain * wildcards."],
};

function permissionCheck(user, room) {
	if (!Rooms(room) && !user.can('seniorstaff')) {
		return false;
	}
	if (!user.can('lock') && !user.can('warn', null, Rooms(room))) {
		return false;
	}
	if (Rooms(room) && Rooms(room).isPrivate && (!user.can('seniorstaff') && !user.can('warn', null, Rooms(room)))) {
		return false;
	}
	if (Rooms(room) && Rooms(room).isPersonal && (!user.can('seniorstaff') && !user.can('warn', null, Rooms(room)))) {
		return false;
	}
	if (toId(room) === 'upperstaff' && !user.can('seniorstaff')) return false;
	return true;
}

function generateTable(array, command) {
	let output = "
";
	let count = 0;
	for (let u in array) {
		if (array[u] === 'today.txt') continue;
		if (count === 0) output += "
";
		output += '<button style="width: 100%" name="send" value="' + command + Tools.escapeHTML(array[u]) + '">' +
		(Rooms(array[u]) ? '' : '') + Tools.escapeHTML(array[u]) + (Rooms(array[u]) ? '' : '') + '</button>	';
		count++;
		if (count > 3) {
			output += '
';
			count = 0;
		}
	}
	output += '
';
	return output;
}

function escapeRegExp(s) {
	return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

function parseMessage(message, user) {
	let timestamp = message.substr(0, 9).trim();
	message = message.substr(9).trim();
	let lineSplit = message.split('|');

	switch (lineSplit[1]) {
	case 'c':
		let name = lineSplit[2];
		if (name === '~') break;
		let highlight = new RegExp("\\b" + toId(user) + "\\b", 'gi');
		let div = "chat";
		if (lineSplit.slice(3).join('|').match(highlight)) div = "chat highlighted";
		message = '[' + timestamp + '] ' + '' + name.substr(0, 1) +
		'' + name.substr(1, name.length) + ':' +
		Wisp.parseMessage(lineSplit.slice(3).join('|')) + '';
		break;
	case 'uhtml':
		message = '' + lineSplit.slice(3).join('|').trim() + '';
		break;
	case 'raw':
	case 'html':
		message = '' + lineSplit.slice(2).join('|').trim() + '';
		break;
	case '':
		message = '' + Tools.escapeHTML(lineSplit.slice(1).join('|')) + '';
		break;
	case 'j':
	case 'J':
	case 'l':
	case 'L':
	case 'N':
	case 'unlink':
	case 'userstats':
	case 'tournament':
	case 'uhtmlchange':
		message = "";
		break;
	default:
		message = '' + Tools.escapeHTML(message) + '';
		break;
	}
	return message;
}
<|endoftext|>


<commit_before>$(function() {
  var press = false;
  var perfiles = [];
  var index = 0;
  var size = 0;

  $.getJSON("/data.json", function (data) {
    data.perfiles.forEach(function(perfil) {
      perfiles.push({
        img: perfil.img,
        page: 'HTML/' + perfil.page,
        name: perfil.name
      });
    });
    size = perfiles.length;
  });

  $('.link').click(function() {
    var id = $(this).attr('id');
    var name = "HTML/" + id + ".html";
    if (id != 'perfiles') window.location = name;
  });

  $('#left').click(function() {
    press = true;
    index--;
    index = (index < 0)? size - 1 : index;
    $('.Perfiles').css('background-image', `url(${perfiles[index].img})`);
  });

  $('#right').click(function() {
    press = true;
    index = (index + 1) % size;
    $('.Perfiles').css('background-image', `url(${perfiles[index].img})`);
  });
});
<commit_msg>Update index.js<commit_after>$(function() {
  var press = false;
  var perfiles = [];
  var index = 0;
  var size = 0;

  $.getJSON("/data.json", function (data) {
    data.perfiles.forEach(function(perfil) {
      perfiles.push({
        "img": perfil.img,
        "page": 'HTML/' + perfil.page,
        "name": perfil.name
      });
    });
    size = perfiles.length;
  });

  $('.link').click(function() {
    var id = $(this).attr('id');
    var name = "HTML/" + id + ".html";
    if (id != 'perfiles') window.location = name;
  });

  $('#left').click(function() {
    press = true;
    index--;
    index = (index < 0)? size - 1 : index;
    $('.Perfiles').css('background-image', `url(${perfiles[index].img})`);
  });

  $('#right').click(function() {
    press = true;
    index = (index + 1) % size;
    $('.Perfiles').css('background-image', `url(${perfiles[index].img})`);
  });
});
<|endoftext|>


<commit_before>var Irelia = require('irelia');
var irelia = new Irelia({
  secure:true,
  host: 'prod.api.pvp.net',
  path: '/api/lol/',
  key: '',
  debug:true
});

var data = [
  {
    value:75,
    color:"#262626",
    highlight: "#505050",
    lable:""
  },
  {
    value:25,
    color:"#ffffff",
    highlight: "#ffffff",
    lable:""
  }
];

var options = {};

var ctx = document.getElementById("champChart").getContext("2d");
var champChart = new Chart(ctx).Doughnut(data,options);

irelia.getChampions('na', true, function(err, summoner){
  if(err){
    if(err.status.code == 429){
                console.log(err.status.message);
            } else if(err.status.code == 404){
                console.log(err.status.message);
            } else if(err.status.code == 500){
                console.log(err.status.message);
            } else {
                console.log('Unknown error code');
            }
        } else {
            console.log(err); // Non http error
        }
  }else{
    document.getElementById("champs").innerText = ('Free champions: ' + champions.join(', ');
  }
});
<commit_msg>js fixes<commit_after>
var data = [
  {
    value:75,
    color:"#262626",
    highlight: "#505050",
    lable:""
  },
  {
    value:25,
    color:"#ffffff",
    highlight: "#ffffff",
    lable:""
  }
];

var options = {};

var ctx = document.getElementById("champChart").getContext("2d");
var champChart = new Chart(ctx).Doughnut(data,options);
<|endoftext|>


<commit_before>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
/*global desc, task, jake, fail, complete, directory*/

(function() {
	"use strict";

	if (!process.env.loose) console.log("For more forgiving test settings, use 'loose=true'");

	var fs = require("fs");
	var shell = require("shelljs");

	var browserify = require("browserify");
	var lint = require("./build/util/lint_runner.js");
	var nodeunit = require("./build/util/nodeunit_runner.js");
	var karma = require("./build/util/karma_runner.js");
	var versionChecker = require("./build/util/version_checker.js");
	var runServer = require("./src/_run_server.js");
	var path = require("path");

	var REQUIRED_BROWSERS = [
		"IE 8.0.0 (Windows 7)",
		"IE 9.0.0 (Windows 7)",
		"Firefox 35.0.0 (Mac OS X 10.10)",
		"Chrome 39.0.2171 (Mac OS X 10.10.1)",
		"Safari 8.0.2 (Mac OS X 10.10.1)",
		"Mobile Safari 7.0.0 (iOS 7.1)"
	];

	var GENERATED_DIR = "generated";
	var TEMP_TESTFILE_DIR = GENERATED_DIR + "/test";
	var BUILD_DIR = GENERATED_DIR + "/build";
	var BUILD_CLIENT_DIR = BUILD_DIR + "/client";

	directory(TEMP_TESTFILE_DIR);
	directory(BUILD_DIR);
	directory(BUILD_CLIENT_DIR);

	desc("Delete all generated files");
	task("clean", [], function() {
		jake.rmRf(GENERATED_DIR);
	});

	desc("Build and test");
	task("default", ["lint", "test"], function() {
		console.log("\n\nOK");
	});

	desc("Start Karma server for testing");
	task("karma", function() {
		karma.serve("build/karma.conf.js", complete, fail);
	}, {async: true});

	desc("Start WeeWikiPaint server for manual testing");
	task("run", ["build"], function() {
		console.log("Running server. Press Ctrl-C to stop.");
		runServer.runInteractively();
		// We never call complete() because we want the task to hang until the user presses 'Ctrl-C'.
	}, {async: true});

	desc("Lint everything");
	task("lint", ["lintNode", "lintClient"]);

	task("lintNode", ["nodeVersion"], function() {
		var passed = lint.validateFileList(nodeLintFiles(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	task("lintClient", function() {
		var passed = lint.validateFileList(clientLintFiles(), clientLintOptions(), clientGlobals());
		if (!passed) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testServer", "testClient", "testSmoke"]);

	desc("Test server code");
	task("testServer", ["nodeVersion", TEMP_TESTFILE_DIR], function() {
		nodeunit.runTests(serverTestFiles(), complete, fail);
	}, {async: true});

	desc("Test client code");
	task("testClient", [], function() {
		karma.runTests(REQUIRED_BROWSERS, complete, fail);
	}, {async: true});

	desc("End-to-end smoke tests");
	task("testSmoke", ["build"], function() {
		nodeunit.runTests(smokeTestFiles(), complete, fail);
	}, {async: true});

	desc("Bundle and build code");
	task("build", [BUILD_CLIENT_DIR], function() {
		shell.rm("-rf", BUILD_CLIENT_DIR + "/*");
		shell.cp(
			"-R",
			"src/client/*.html", "src/client/*.css", "src/client/images", "src/client/vendor",
			BUILD_CLIENT_DIR
		);

		console.log("Bundling client files with Browserify...");
		var b = browserify({ debug: true });
		b.require("./src/client/client.js", {expose: "./client.js"} );
		b.require("./src/client/html_element.js", {expose: "./html_element.js"} );
		b.bundle(function(err, bundle) {
			if (err) fail(err);
			fs.writeFileSync(BUILD_CLIENT_DIR + "/bundle.js", bundle);
			complete();
		});
	}, {async: true});

	desc("Deploy to Heroku");
	task("deploy", function() {
		console.log("To deploy to production:");
		console.log("1. Integrate ('jake integrate')");

		// Correction: Use "git push heroku integration:master" to deploy from integration branch.
		// Thanks to Jüri A, http://www.letscodejavascript.com/v3/comments/live/32#comment-798947003 .
		console.log("2. 'git push heroku integration:master' (or 'git push staging integration:master')");
		console.log("3. 'jake test'");
		console.log();
		console.log("To deploy latest code to staging server:");
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. 'git push staging master");
		console.log("3. Visit http://wwp-staging.herokuapp.com/");
	});

//	desc("Ensure correct version of Node is present.");
	task("nodeVersion", [], function() {
		var deployedVersion = "v" + require("./package.json").engines.node;
		versionChecker.check("Node", !process.env.loose, deployedVersion, process.version, fail);
	});

	desc("Integration checklist");
	task("integrate", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. Build on the integration box.");
		console.log("   a. Walk over to integration box.");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("   f. If jake fails, stop! Try again after fixing the issue.");
		console.log("3. 'git checkout integration'");
		console.log("4. 'git merge master --no-ff --log'");
		console.log("5. 'git checkout master'");
	});

	desc("End-of-episode checklist");
	task("episode", [], function() {
		console.log("1. Save recording.");
		console.log("2. Double-check sound and framing.");
		console.log("3. Commit source code.");
		console.log("4. Check Windows compatibility");
		console.log("   a. Switch to Windows VM");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("5. Tag episode: 'git tag -a episodeXX -m \"End of episode XX\"'");
	});

	function serverTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/server/**/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function smokeTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function nodeLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("*.js");
		javascriptFiles.include("build/util/*.js");
		javascriptFiles.include("src/server/**/*.js");
		javascriptFiles.include("src/*.js");
		return javascriptFiles.toArray();
	}

	function clientLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("src/client/*.js");
		return javascriptFiles.toArray();
	}

	function nodeLintOptions() {
		var options = sharedLintOptions();
		options.node = true;
		return options;
	}

	function clientLintOptions() {
		var options = sharedLintOptions();
		options.browser = true;
		return options;
	}

	function sharedLintOptions() {
		return {
			bitwise: true,
			curly: false,
			eqeqeq: true,
			forin: true,
			immed: true,
			latedef: false,
			newcap: true,
			noarg: true,
			noempty: true,
			nonew: true,
			regexp: true,
			undef: true,
			strict: true,
			trailing: true
		};
	}

	function clientGlobals() {
		return {
			// Browserify
			require: false,
			module: false,
			exports: false,

			// Mocha / expect.js
			describe: false,
			it: false,
			expect: false,
			dump: false,
			beforeEach: false,
			afterEach: false,
			before: false,
			after: false,

			// Browser
			console: false
		};
	}

}());
<commit_msg>Upgraded Chrome 39.0.2171 --> 40.0.2214<commit_after>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
/*global desc, task, jake, fail, complete, directory*/

(function() {
	"use strict";

	if (!process.env.loose) console.log("For more forgiving test settings, use 'loose=true'");

	var fs = require("fs");
	var shell = require("shelljs");

	var browserify = require("browserify");
	var lint = require("./build/util/lint_runner.js");
	var nodeunit = require("./build/util/nodeunit_runner.js");
	var karma = require("./build/util/karma_runner.js");
	var versionChecker = require("./build/util/version_checker.js");
	var runServer = require("./src/_run_server.js");
	var path = require("path");

	var REQUIRED_BROWSERS = [
		"IE 8.0.0 (Windows 7)",
		"IE 9.0.0 (Windows 7)",
		"Firefox 35.0.0 (Mac OS X 10.10)",
		"Chrome 40.0.2214 (Mac OS X 10.10.1)",
		"Safari 8.0.2 (Mac OS X 10.10.1)",
		"Mobile Safari 7.0.0 (iOS 7.1)"
	];

	var GENERATED_DIR = "generated";
	var TEMP_TESTFILE_DIR = GENERATED_DIR + "/test";
	var BUILD_DIR = GENERATED_DIR + "/build";
	var BUILD_CLIENT_DIR = BUILD_DIR + "/client";

	directory(TEMP_TESTFILE_DIR);
	directory(BUILD_DIR);
	directory(BUILD_CLIENT_DIR);

	desc("Delete all generated files");
	task("clean", [], function() {
		jake.rmRf(GENERATED_DIR);
	});

	desc("Build and test");
	task("default", ["lint", "test"], function() {
		console.log("\n\nOK");
	});

	desc("Start Karma server for testing");
	task("karma", function() {
		karma.serve("build/karma.conf.js", complete, fail);
	}, {async: true});

	desc("Start WeeWikiPaint server for manual testing");
	task("run", ["build"], function() {
		console.log("Running server. Press Ctrl-C to stop.");
		runServer.runInteractively();
		// We never call complete() because we want the task to hang until the user presses 'Ctrl-C'.
	}, {async: true});

	desc("Lint everything");
	task("lint", ["lintNode", "lintClient"]);

	task("lintNode", ["nodeVersion"], function() {
		var passed = lint.validateFileList(nodeLintFiles(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	task("lintClient", function() {
		var passed = lint.validateFileList(clientLintFiles(), clientLintOptions(), clientGlobals());
		if (!passed) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testServer", "testClient", "testSmoke"]);

	desc("Test server code");
	task("testServer", ["nodeVersion", TEMP_TESTFILE_DIR], function() {
		nodeunit.runTests(serverTestFiles(), complete, fail);
	}, {async: true});

	desc("Test client code");
	task("testClient", [], function() {
		karma.runTests(REQUIRED_BROWSERS, complete, fail);
	}, {async: true});

	desc("End-to-end smoke tests");
	task("testSmoke", ["build"], function() {
		nodeunit.runTests(smokeTestFiles(), complete, fail);
	}, {async: true});

	desc("Bundle and build code");
	task("build", [BUILD_CLIENT_DIR], function() {
		shell.rm("-rf", BUILD_CLIENT_DIR + "/*");
		shell.cp(
			"-R",
			"src/client/*.html", "src/client/*.css", "src/client/images", "src/client/vendor",
			BUILD_CLIENT_DIR
		);

		console.log("Bundling client files with Browserify...");
		var b = browserify({ debug: true });
		b.require("./src/client/client.js", {expose: "./client.js"} );
		b.require("./src/client/html_element.js", {expose: "./html_element.js"} );
		b.bundle(function(err, bundle) {
			if (err) fail(err);
			fs.writeFileSync(BUILD_CLIENT_DIR + "/bundle.js", bundle);
			complete();
		});
	}, {async: true});

	desc("Deploy to Heroku");
	task("deploy", function() {
		console.log("To deploy to production:");
		console.log("1. Integrate ('jake integrate')");

		// Correction: Use "git push heroku integration:master" to deploy from integration branch.
		// Thanks to Jüri A, http://www.letscodejavascript.com/v3/comments/live/32#comment-798947003 .
		console.log("2. 'git push heroku integration:master' (or 'git push staging integration:master')");
		console.log("3. 'jake test'");
		console.log();
		console.log("To deploy latest code to staging server:");
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. 'git push staging master");
		console.log("3. Visit http://wwp-staging.herokuapp.com/");
	});

//	desc("Ensure correct version of Node is present.");
	task("nodeVersion", [], function() {
		var deployedVersion = "v" + require("./package.json").engines.node;
		versionChecker.check("Node", !process.env.loose, deployedVersion, process.version, fail);
	});

	desc("Integration checklist");
	task("integrate", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. Build on the integration box.");
		console.log("   a. Walk over to integration box.");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("   f. If jake fails, stop! Try again after fixing the issue.");
		console.log("3. 'git checkout integration'");
		console.log("4. 'git merge master --no-ff --log'");
		console.log("5. 'git checkout master'");
	});

	desc("End-of-episode checklist");
	task("episode", [], function() {
		console.log("1. Save recording.");
		console.log("2. Double-check sound and framing.");
		console.log("3. Commit source code.");
		console.log("4. Check Windows compatibility");
		console.log("   a. Switch to Windows VM");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("5. Tag episode: 'git tag -a episodeXX -m \"End of episode XX\"'");
	});

	function serverTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/server/**/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function smokeTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function nodeLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("*.js");
		javascriptFiles.include("build/util/*.js");
		javascriptFiles.include("src/server/**/*.js");
		javascriptFiles.include("src/*.js");
		return javascriptFiles.toArray();
	}

	function clientLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("src/client/*.js");
		return javascriptFiles.toArray();
	}

	function nodeLintOptions() {
		var options = sharedLintOptions();
		options.node = true;
		return options;
	}

	function clientLintOptions() {
		var options = sharedLintOptions();
		options.browser = true;
		return options;
	}

	function sharedLintOptions() {
		return {
			bitwise: true,
			curly: false,
			eqeqeq: true,
			forin: true,
			immed: true,
			latedef: false,
			newcap: true,
			noarg: true,
			noempty: true,
			nonew: true,
			regexp: true,
			undef: true,
			strict: true,
			trailing: true
		};
	}

	function clientGlobals() {
		return {
			// Browserify
			require: false,
			module: false,
			exports: false,

			// Mocha / expect.js
			describe: false,
			it: false,
			expect: false,
			dump: false,
			beforeEach: false,
			afterEach: false,
			before: false,
			after: false,

			// Browser
			console: false
		};
	}

}());
<|endoftext|>


<commit_before>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
/*global desc, task, jake, fail, complete, directory, require, console, process */
(function () {
	"use strict";

	var REQUIRED_BROWSERS = [
		"Safari 6.0"
	];

	var lint = require("./build/lint_runner.js");
	var nodeunit = require("./build/nodeunit_runner.js");
	var testacular = require("./build/testacular_runner.js");

	desc("Lint and test");
	task("default", ["lint", "test"], function() {
		console.log("\n\nOK");
	});

	desc("Start Testacular server for testing");
	task("testacular", function() {
		testacular.serve(complete, fail);
	}, {async: true});

	desc("Lint everything");
	task("lint", [], function () {
		var passed = lint.validateFileList(nodeFilesToLint(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testClient"], function() {
		nodeunit.runTests(nodeFilesToTest(), function(failures) {
			if (failures) fail("Tests failed");
			complete();
		});
	}, {async: true});

	desc("Test client code");
	task("testClient", function() {
		testacular.runTests(REQUIRED_BROWSERS, complete, fail);
	}, {async: true});

	function nodeFilesToTest() {
		var testFiles = new jake.FileList();
		testFiles.include("**/_*_test.js");
		testFiles.exclude("node_modules");
		var tests = testFiles.toArray();
		return tests;
	}

	function nodeFilesToLint() {
		var files = new jake.FileList();
		files.include("src/**/*.js");
		files.include("build/**/*.js");
		files.include("Jakefile.js");
		files.exclude("build/testacular.conf.js");
		var fileList = files.toArray();
		return fileList;
	}

	function nodeLintOptions() {
		return {
			bitwise:true,
			curly:false,
			eqeqeq:true,
			forin:true,
			immed:true,
			latedef:false,
			newcap:true,
			noarg:true,
			noempty:true,
			nonew:true,
			regexp:true,
			undef:true,
			strict:true,
			trailing:true,
			node:true
		};
	}
}());<commit_msg>Factored out 'testServer' target<commit_after>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
/*global desc, task, jake, fail, complete, directory, require, console, process */
(function () {
	"use strict";

	var REQUIRED_BROWSERS = [
		"Safari 6.0"
	];

	var lint = require("./build/lint_runner.js");
	var nodeunit = require("./build/nodeunit_runner.js");
	var testacular = require("./build/testacular_runner.js");

	desc("Lint and test");
	task("default", ["lint", "test"], function() {
		console.log("\n\nOK");
	});

	desc("Start Testacular server for testing");
	task("testacular", function() {
		testacular.serve(complete, fail);
	}, {async: true});

	desc("Lint everything");
	task("lint", [], function () {
		var passed = lint.validateFileList(nodeFilesToLint(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testServer", "testClient"]);

	task("testServer", function() {
		nodeunit.runTests(nodeFilesToTest(), function(failures) {
			if (failures) fail("Tests failed");
			complete();
		});
	}, {async: true});

	desc("Test client code");
	task("testClient", function() {
		testacular.runTests(REQUIRED_BROWSERS, complete, fail);
	}, {async: true});

	function nodeFilesToTest() {
		var testFiles = new jake.FileList();
		testFiles.include("**/_*_test.js");
		testFiles.exclude("node_modules");
		var tests = testFiles.toArray();
		return tests;
	}

	function nodeFilesToLint() {
		var files = new jake.FileList();
		files.include("src/**/*.js");
		files.include("build/**/*.js");
		files.include("Jakefile.js");
		files.exclude("build/testacular.conf.js");
		var fileList = files.toArray();
		return fileList;
	}

	function nodeLintOptions() {
		return {
			bitwise:true,
			curly:false,
			eqeqeq:true,
			forin:true,
			immed:true,
			latedef:false,
			newcap:true,
			noarg:true,
			noempty:true,
			nonew:true,
			regexp:true,
			undef:true,
			strict:true,
			trailing:true,
			node:true
		};
	}
}());<|endoftext|>


<commit_before>export { UrlComposer };

/**
 * Compose a URL for downloading cells data (as JSON or .CSV format) from a Google Sheets by using Google Visualzation Table Query
 * API. The target spreadsheet should be shared by either "Public on the web" or "Anyone with the link".
 *
 * (GVizTQ = Google Visualzation Table Query)
 *
 * The follwoings are some composed examples:
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A1&tqx=version:0.6;responseHandler:SetData
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A1&tqx=out:json;responseHandler:SetData
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A1&tqx=out:csv
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A1&tqx=out:html
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=816353147&range=AH59:AK98&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=1504323928&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?sheet=Evolution.Param&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=Evolution.Param!B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range='Evolution.Param'!B17&headers=0&tqx=out:csv
 *
 */
class UrlComposer {

  /**
   * If sheetId is null, sheetName is null, and no sheet name in the range's A1 notation, the first (most left) visible sheet
   * inside the spreadsheet will be used.
   *
   * @param {string} spreadsheetId
   *   The component after the "https://docs.google.com/spreadsheets/d/".
   *
   * @param {string} range
   *   The cells' A1 notation of the cells. It describes the (name and) range of the sheet inside the spreadsheet.
   *   - "A1" refers to one cell of the first (most left) visible sheet.
   *   - "B2:C5" refers to cells of a rectangle of the first (most left) visible sheet.
   *   - "Books!D8:D" refers to the column D of sheet named "Books" from rows 8 to the last rows.
   *   - "'Name has space'!7:10" refers to the rows 7 to 10 of sheet named "Name has space".
   *
   * @param {number} headers
   *   The component after the "headers=". It means how many header rows. It should be an zero or positive integer.
   *
   * @param {string} responseHandler
   *   The function name of JSON content handler. Only meaningful when the content is downloaded as JSONP format. This
   * responseHandler name will be prepended in front of the downloaded content.
   *
   * @param {number} sheetId
   *   The component after the "gid=". If the sheetName is used (or the sheet name in the range's A1 notation is specified),
   * keep this sheetId null (or undefined).
   *
   * @param {string} sheetName
   *   The component after the "sheet=". If the sheetId is used (or the sheet name in in the range's A1 notation is specified),
   * keep this sheetName null (or undefined).
   *
   * @see {@link https://developers.google.com/sheets/api/guides/concepts}
   * @see {@link https://developers.google.com/chart/interactive/docs/dev/implementing_data_source}
   * @see {@link https://developers.google.com/chart/interactive/docs/spreadsheets}
   * @see {@link https://developers.google.com/chart/interactive/docs/querylanguage}
   */
  constructor( spreadsheetId, range, headers = 0, responseHandler = null, sheetId = null, sheetName = null ) {
    this.spreadsheetId = spreadsheetId;
    this.range = range;
    this.headers = headers;
    this.responseHandler = responseHandler;
    this.sheetId = sheetId;
    this.sheetName = sheetName;
  }

  /**
   * @param {string} outputFormat
   *   Specify the data format when downloading the returned url. It should be null or "json" or "csv" or "html". If null, there
   * will be no format specified in the generated url (means default format, usually same as "json").
   *
   * @return {string} The url for downloading the target as specified format.
   */
  getUrl_forFormat( outputFormat ) {
    // Because sheetId could be 0, it should be checked by comparing to null directly (i.e. should not use ( !this.sheetId )).
    let url = `${UrlComposer.spreadsheetUrlPrefix}/${encodeURIComponent(this.spreadsheetId)}/${

      UrlComposer.GoogleVisualizationTableQueryUrlPostfix}?tqx=version:${
      UrlComposer.GoogleVisualizationTableQueryAPIVersion}${
      ( outputFormat != null ) ? `;out:${encodeURIComponent(outputFormat)}` : "" }${
      ( this.responseHandler != null ) ? `;responseHandler=${encodeURIComponent(this.responseHandler)}` : "" }${

      ( this.sheetId != null ) ? `&gid=${encodeURIComponent(this.sheetId)}` : `${
      ( this.sheetName != null ) ? `&sheet=${encodeURIComponent(this.sheetName)}` : "" }` }${
      ( this.range != null ) ? `&range=${encodeURIComponent(this.range)}` : "" }&headers=${encodeURIComponent(this.headers)}`;

    return url;
  }

  /**
   * @return {string} The url for downloading the target as JSONP format.
   */
  getUrl_forJSON() {
    //return this.getUrl_forFormat( "json" );
    return this.getUrl_forFormat( null ); // Because default format is "json".
  }

  /**
   * @return {string} The url for downloading the target as CSV format.
   */
  getUrl_forCSV() {
    return this.getUrl_forFormat( "csv" );
  }

  /**
   * @return {string} The url for downloading the target as HTML format.
   */
  getUrl_forHTML() {
    return this.getUrl_forFormat( "html" );
  }

  /**
   * Evaluate JSONP string by JSON.parse().
   *
   * A JSONP string looks like:
   * @example
   *   Handler( ... );
   *
   * This method will find the position of the first "(" and the last ")". Extract the string between these two positions.
   * Parsing it by JSON.parse().
   *
   * @param {string} strJSONP
   *   The JSONP string to be parsed.
   *
   * @return {Object|value}
   *   Return the JSON parsed result. Return null, if left parenthesis or right parenthesis is not found.
   */
  static evalJSONP( strJSONP ) {
    let beginIndex = strJSONP.indexOf( "(" );
    if ( beginIndex < 0 )
      return null;  // left parenthesis is not found.

    let endIndex = strJSONP.lastIndexOf( ")" );
    if ( endIndex < 0 )
      return null;  // right parenthesis is not found.

    let strJSON = strJSONP.slice( beginIndex + 1, endIndex ); // "+ 1" for the next position of the left parenthesis.
    let result = JSON.parse( strJSON );
    return result;
  }
}

/** The url prefix of Google Sheets. */
UrlComposer.spreadsheetUrlPrefix = "https://docs.google.com/spreadsheets/d";

/** The url postfix of Google Visualization Table Query. */
UrlComposer.GoogleVisualizationTableQueryUrlPostfix = "gviz/tq";

/** The version of Google Visualization Table Query API. */
UrlComposer.GoogleVisualizationTableQueryAPIVersion = "0.6";
<commit_msg>Update GVizTQ.js<commit_after>export { UrlComposer };

/**
 * Compose a URL for downloading cells data (as JSON or .CSV format) from a Google Sheets by using Google Visualzation Table Query
 * API. The target spreadsheet should be shared by either "Public on the web" or "Anyone with the link".
 *
 * (GVizTQ = Google Visualzation Table Query)
 *
 * The follwoings are some composed examples:
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=version:0.6
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=version:0.6;responseHandler:SetData
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:json;responseHandler:SetData
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:csv
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:html
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=816353147&range=AH55:AK94&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=1504323928&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?sheet=Evolution.Param&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=Evolution.Param!B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range='Evolution.Param'!B17&headers=0&tqx=out:csv
 *
 */
class UrlComposer {

  /**
   * If sheetId is null, sheetName is null, and no sheet name in the range's A1 notation, the first (most left) visible sheet
   * inside the spreadsheet will be used.
   *
   * @param {string} spreadsheetId
   *   The component after the "https://docs.google.com/spreadsheets/d/".
   *
   * @param {string} range
   *   The cells' A1 notation of the cells. It describes the (name and) range of the sheet inside the spreadsheet.
   *   - "A1" refers to one cell of the first (most left) visible sheet.
   *   - "B2:C5" refers to cells of a rectangle of the first (most left) visible sheet.
   *   - "Books!D8:D" refers to the column D of sheet named "Books" from rows 8 to the last rows.
   *   - "'Name has space'!7:10" refers to the rows 7 to 10 of sheet named "Name has space".
   *
   * @param {number} headers
   *   The component after the "headers=". It means how many header rows. It should be an zero or positive integer.
   *
   * @param {string} responseHandler
   *   The function name of JSON content handler. Only meaningful when the content is downloaded as JSONP format. This
   * responseHandler name will be prepended in front of the downloaded content.
   *
   * @param {number} sheetId
   *   The component after the "gid=". If the sheetName is used (or the sheet name in the range's A1 notation is specified),
   * keep this sheetId null (or undefined).
   *
   * @param {string} sheetName
   *   The component after the "sheet=". If the sheetId is used (or the sheet name in in the range's A1 notation is specified),
   * keep this sheetName null (or undefined).
   *
   * @see {@link https://developers.google.com/sheets/api/guides/concepts}
   * @see {@link https://developers.google.com/chart/interactive/docs/dev/implementing_data_source}
   * @see {@link https://developers.google.com/chart/interactive/docs/spreadsheets}
   * @see {@link https://developers.google.com/chart/interactive/docs/querylanguage}
   */
  constructor( spreadsheetId, range, headers = 0, responseHandler = null, sheetId = null, sheetName = null ) {
    this.spreadsheetId = spreadsheetId;
    this.range = range;
    this.headers = headers;
    this.responseHandler = responseHandler;
    this.sheetId = sheetId;
    this.sheetName = sheetName;
  }

  /**
   * @param {string} outputFormat
   *   Specify the data format when downloading the returned url. It should be null or "json" or "csv" or "html". If null, there
   * will be no format specified in the generated url (means default format, usually same as "json").
   *
   * @return {string} The url for downloading the target as specified format.
   */
  getUrl_forFormat( outputFormat ) {
    // Because sheetId could be 0, it should be checked by comparing to null directly (i.e. should not use ( !this.sheetId )).
    let url = `${UrlComposer.spreadsheetUrlPrefix}/${encodeURIComponent(this.spreadsheetId)}/${

      UrlComposer.GoogleVisualizationTableQueryUrlPostfix}?tqx=version:${
      UrlComposer.GoogleVisualizationTableQueryAPIVersion}${
      ( outputFormat != null ) ? `;out:${encodeURIComponent(outputFormat)}` : "" }${
      ( this.responseHandler != null ) ? `;responseHandler=${encodeURIComponent(this.responseHandler)}` : "" }${

      ( this.sheetId != null ) ? `&gid=${encodeURIComponent(this.sheetId)}` : `${
      ( this.sheetName != null ) ? `&sheet=${encodeURIComponent(this.sheetName)}` : "" }` }${
      ( this.range != null ) ? `&range=${encodeURIComponent(this.range)}` : "" }&headers=${encodeURIComponent(this.headers)}`;

    return url;
  }

  /**
   * @return {string} The url for downloading the target as JSONP format.
   */
  getUrl_forJSON() {
    //return this.getUrl_forFormat( "json" );
    return this.getUrl_forFormat( null ); // Because default format is "json".
  }

  /**
   * @return {string} The url for downloading the target as CSV format.
   */
  getUrl_forCSV() {
    return this.getUrl_forFormat( "csv" );
  }

  /**
   * @return {string} The url for downloading the target as HTML format.
   */
  getUrl_forHTML() {
    return this.getUrl_forFormat( "html" );
  }

  /**
   * Evaluate JSONP string by JSON.parse().
   *
   * A JSONP string looks like:
   * @example
   *   Handler( ... );
   *
   * This method will find the position of the first "(" and the last ")". Extract the string between these two positions.
   * Parsing it by JSON.parse().
   *
   * @param {string} strJSONP
   *   The JSONP string to be parsed.
   *
   * @return {Object|value}
   *   Return the JSON parsed result. Return null, if left parenthesis or right parenthesis is not found.
   */
  static evalJSONP( strJSONP ) {
    let beginIndex = strJSONP.indexOf( "(" );
    if ( beginIndex < 0 )
      return null;  // left parenthesis is not found.

    let endIndex = strJSONP.lastIndexOf( ")" );
    if ( endIndex < 0 )
      return null;  // right parenthesis is not found.

    let strJSON = strJSONP.slice( beginIndex + 1, endIndex ); // "+ 1" for the next position of the left parenthesis.
    let result = JSON.parse( strJSON );
    return result;
  }
}

/** The url prefix of Google Sheets. */
UrlComposer.spreadsheetUrlPrefix = "https://docs.google.com/spreadsheets/d";

/** The url postfix of Google Visualization Table Query. */
UrlComposer.GoogleVisualizationTableQueryUrlPostfix = "gviz/tq";

/** The version of Google Visualization Table Query API. */
UrlComposer.GoogleVisualizationTableQueryAPIVersion = "0.6";
<|endoftext|>


<commit_before>(function() {
	"use strict";

	var root = this,
		Chart = root.Chart,
		helpers = Chart.helpers;

	var time = {
		units: [
			'millisecond',
			'second',
			'minute',
			'hour',
			'day',
			'week',
			'month',
			'quarter',
			'year',
		],
		unit: {
			'millisecond': {
				display: 'SSS [ms]', // 002 ms
				maxStep: 1000,
			},
			'second': {
				display: 'h:mm:ss a', // 11:20:01 AM
				maxStep: 60,
			},
			'minute': {
				display: 'h:mm:ss a', // 11:20:01 AM
				maxStep: 60,
			},
			'hour': {
				display: 'MMM D, hA', // Sept 4, 5PM
				maxStep: 24,
			},
			'day': {
				display: 'll', // Sep 4 2015
				maxStep: 7,
			},
			'week': {
				display: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
				maxStep: 4.3333,
			},
			'month': {
				display: 'MMM YYYY', // Sept 2015
				maxStep: 12,
			},
			'quarter': {
				display: '[Q]Q - YYYY', // Q3
				maxStep: 4,
			},
			'year': {
				display: 'YYYY', // 2015
				maxStep: false,
			},
		}
	};

	var defaultConfig = {
		position: "bottom",

		time: {
			format: false, // false == date objects or use pattern string from http://momentjs.com/docs/#/parsing/string-format/
			unit: false, // false == automatic or override with week, month, year, etc.
			round: false, // none, or override with week, month, year, etc.
			displayFormat: false, // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
		},
	};

	var TimeScale = Chart.Scale.extend({
		buildTicks: function(index) {

			this.ticks = [];
			this.labelMoments = [];

			// Parse each label into a moment
			this.data.labels.forEach(function(label, index) {
				var labelMoment = this.parseTime(label);
				if (this.options.time.round) {
					labelMoment.startOf(this.options.time.round);
				}
				this.labelMoments.push(labelMoment);
			}, this);

			// Find the first and last moments, and range
			this.firstTick = moment.min.call(this, this.labelMoments).clone();
			this.lastTick = moment.max.call(this, this.labelMoments).clone();

			// Set unit override if applicable
			if (this.options.time.unit) {
				this.tickUnit = this.options.time.unit || 'day';
				this.displayFormat = time.unit[this.tickUnit].display;
				this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true));
			} else {
				// Determine the smallest needed unit of the time
				var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
				var labelCapacity = innerWidth / this.options.ticks.fontSize + 4;
				var buffer = this.options.time.round ? 0 : 2;

				this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, true) + buffer);
				var done;

				helpers.each(time.units, function(format) {
					if (this.tickRange <= labelCapacity) {
						return;
					}
					this.tickUnit = format;
					this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit) + buffer);
					this.displayFormat = time.unit[format].display;

				}, this);
			}

			this.firstTick.startOf(this.tickUnit);
			this.lastTick.endOf(this.tickUnit);
			this.smallestLabelSeparation = this.width;

			var i = 0;

			for (i = 1; i < this.labelMoments.length; i++) {
				this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[i].diff(this.labelMoments[i - 1], this.tickUnit, true));
			}

			// Tick displayFormat override
			if (this.options.time.displayFormat) {
				this.displayFormat = this.options.time.displayFormat;
			}

			// For every unit in between the first and last moment, create a moment and add it to the ticks tick
			for (i = 0; i <= this.tickRange; ++i) {
				this.ticks.push(this.firstTick.clone().add(i, this.tickUnit));
			}
		},
		convertTicksToLabels: function() {
			this.ticks = this.ticks.map(function(tick, index, ticks) {
				var formattedTick = tick.format(this.options.time.displayFormat ? this.options.time.displayFormat : time.unit[this.tickUnit].display);

				if (this.options.ticks.userCallback) {
					return this.options.ticks.userCallback(formattedTick, index, ticks);
				} else {
					return formattedTick;
				}
			}, this);
		},
		getPixelForValue: function(value, index, datasetIndex, includeOffset) {

			var offset = this.labelMoments[index].diff(this.firstTick, this.tickUnit, true);

			var decimal = offset / this.tickRange;

			if (this.isHorizontal()) {
				var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
				var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1);
				var valueOffset = (innerWidth * decimal) + this.paddingLeft;

				return this.left + Math.round(valueOffset);
			} else {
				//return this.top + (decimal * (this.height / this.ticks.length));
				var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
				var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1);
				var heightOffset = (innerHeight * decimal) + this.paddingTop;

				return this.top + Math.round(heightOffset);
			}
		},
		parseTime: function(label) {
			// Date objects
			if (typeof label.getMonth === 'function' || typeof label == 'number') {
				return moment(label);
			}
			// Moment support
			if (label.isValid && label.isValid()) {
				return label;
			}
			// Custom parsing (return an instance of moment)
			if (typeof this.options.time.format !== 'string' && this.options.time.format.call) {
				return this.options.time.format(label);
			}
			// Moment format parsing
			return moment(label, this.options.time.format);
		},
	});
	Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig);

}).call(this);
<commit_msg>Time Scale now requires and warns for Moment.js dep<commit_after>(function() {
	"use strict";

	if (!window.moment) {
		console.warn('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at http://momentjs.com/');
		return;
	}

	var root = this,
		Chart = root.Chart,
		helpers = Chart.helpers;

	var time = {
		units: [
			'millisecond',
			'second',
			'minute',
			'hour',
			'day',
			'week',
			'month',
			'quarter',
			'year',
		],
		unit: {
			'millisecond': {
				display: 'SSS [ms]', // 002 ms
				maxStep: 1000,
			},
			'second': {
				display: 'h:mm:ss a', // 11:20:01 AM
				maxStep: 60,
			},
			'minute': {
				display: 'h:mm:ss a', // 11:20:01 AM
				maxStep: 60,
			},
			'hour': {
				display: 'MMM D, hA', // Sept 4, 5PM
				maxStep: 24,
			},
			'day': {
				display: 'll', // Sep 4 2015
				maxStep: 7,
			},
			'week': {
				display: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
				maxStep: 4.3333,
			},
			'month': {
				display: 'MMM YYYY', // Sept 2015
				maxStep: 12,
			},
			'quarter': {
				display: '[Q]Q - YYYY', // Q3
				maxStep: 4,
			},
			'year': {
				display: 'YYYY', // 2015
				maxStep: false,
			},
		}
	};

	var defaultConfig = {
		position: "bottom",

		time: {
			format: false, // false == date objects or use pattern string from http://momentjs.com/docs/#/parsing/string-format/
			unit: false, // false == automatic or override with week, month, year, etc.
			round: false, // none, or override with week, month, year, etc.
			displayFormat: false, // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
		},
	};

	var TimeScale = Chart.Scale.extend({
		buildTicks: function(index) {

			this.ticks = [];
			this.labelMoments = [];

			// Parse each label into a moment
			this.data.labels.forEach(function(label, index) {
				var labelMoment = this.parseTime(label);
				if (this.options.time.round) {
					labelMoment.startOf(this.options.time.round);
				}
				this.labelMoments.push(labelMoment);
			}, this);

			// Find the first and last moments, and range
			this.firstTick = moment.min.call(this, this.labelMoments).clone();
			this.lastTick = moment.max.call(this, this.labelMoments).clone();

			// Set unit override if applicable
			if (this.options.time.unit) {
				this.tickUnit = this.options.time.unit || 'day';
				this.displayFormat = time.unit[this.tickUnit].display;
				this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true));
			} else {
				// Determine the smallest needed unit of the time
				var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
				var labelCapacity = innerWidth / this.options.ticks.fontSize + 4;
				var buffer = this.options.time.round ? 0 : 2;

				this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, true) + buffer);
				var done;

				helpers.each(time.units, function(format) {
					if (this.tickRange <= labelCapacity) {
						return;
					}
					this.tickUnit = format;
					this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit) + buffer);
					this.displayFormat = time.unit[format].display;

				}, this);
			}

			this.firstTick.startOf(this.tickUnit);
			this.lastTick.endOf(this.tickUnit);
			this.smallestLabelSeparation = this.width;

			var i = 0;

			for (i = 1; i < this.labelMoments.length; i++) {
				this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[i].diff(this.labelMoments[i - 1], this.tickUnit, true));
			}

			// Tick displayFormat override
			if (this.options.time.displayFormat) {
				this.displayFormat = this.options.time.displayFormat;
			}

			// For every unit in between the first and last moment, create a moment and add it to the ticks tick
			for (i = 0; i <= this.tickRange; ++i) {
				this.ticks.push(this.firstTick.clone().add(i, this.tickUnit));
			}
		},
		convertTicksToLabels: function() {
			this.ticks = this.ticks.map(function(tick, index, ticks) {
				var formattedTick = tick.format(this.options.time.displayFormat ? this.options.time.displayFormat : time.unit[this.tickUnit].display);

				if (this.options.ticks.userCallback) {
					return this.options.ticks.userCallback(formattedTick, index, ticks);
				} else {
					return formattedTick;
				}
			}, this);
		},
		getPixelForValue: function(value, index, datasetIndex, includeOffset) {

			var offset = this.labelMoments[index].diff(this.firstTick, this.tickUnit, true);

			var decimal = offset / this.tickRange;

			if (this.isHorizontal()) {
				var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
				var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1);
				var valueOffset = (innerWidth * decimal) + this.paddingLeft;

				return this.left + Math.round(valueOffset);
			} else {
				//return this.top + (decimal * (this.height / this.ticks.length));
				var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
				var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1);
				var heightOffset = (innerHeight * decimal) + this.paddingTop;

				return this.top + Math.round(heightOffset);
			}
		},
		parseTime: function(label) {
			// Date objects
			if (typeof label.getMonth === 'function' || typeof label == 'number') {
				return moment(label);
			}
			// Moment support
			if (label.isValid && label.isValid()) {
				return label;
			}
			// Custom parsing (return an instance of moment)
			if (typeof this.options.time.format !== 'string' && this.options.time.format.call) {
				return this.options.time.format(label);
			}
			// Moment format parsing
			return moment(label, this.options.time.format);
		},
	});
	Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig);

}).call(this);
<|endoftext|>


<commit_before>/**
 * scania-angular-ui
 * https://github.com/scania-bootstrap/scania-angular-ui
 * License: MIT
 *
 *
 */

(function () {
    'use strict';

    angular.module('scania.angular.ui', ['scania.angular.lightbox', 'scania.angular.select2']);

    /**
     * @ngdoc module
     * @name scania.angular.lightbox
     *
     * @description
     * Scania lightbox module
     */
    angular.module('scania.angular.lightbox', ['flow', 'ui.bootstrap']).directive('scLightbox', ['$scmodal', scLightbox]);
    /**
     /**
     * @ngdoc directive
     * @name scLightbox
     * @module scania.angular.lightbox
     *
     * @description file upload and gallery extension on angularJs framework
     * @param $scmodal
     * @returns {{restrict: string, templateUrl: string, controllerAs: string, controller: Function}}
     */

    function scLightbox($scmodal) {

        return {
            restrict: 'AEC',
            templateUrl: 'template/scania-angular-upload.html',
            controllerAs: 'lightbox',
            controller: function ($scope, $attrs) {
                $animate.enabled(false); // to solve the conflict between ngAnimate and ui-bootstrap animate
                var self = this;

                self.interval = $attrs.interval;
                self.open = function (index) {
                    $scope.slides = self.flow.files;
                    $scope.slides[index].active = true;
                    self.activeImage = $scope.slides[index];

                    self.modalInstance = $scmodal.open({
                        animation: $attrs.animation,
                        templateUrl: 'template/scania-angular-lightbox.html',
                        size: $attrs.size,
                        windowClass: $attrs.windowclass,
                        scope: $scope,
                        resolve: {
                            slides: function () {
                                return $scope.slides;
                            }
                        },
                        controller: function ($scope) {
                            $scope.setAsActive = function (slide) {
                                if (slide.active) {
                                    self.activeImage = slide;
                                }
                            };
                        }
                    });
                };
                self.deleteImage = function () {
                    self.activeImage.cancel();
                    if ($scope.slides.length === 0) {
                        self.modalInstance.dismiss();
                    }
                };
            }
        };
    }

    /**
     * @ngdoc module
     * @name scania.angular.select2
     *
     * @description
     * Scania select2 directive module
     */
    angular.module('scania.angular.select2', []).directive('scSelect2', ['$compile', '$timeout', scSelect2]);

    /**
     * @ngdoc directive
     * @name scLightbox
     * @module scania.angular.select2
     *
     * @description AngularJS directive for Select2
     * @param $compile
     * @param $timeout
     * @returns {{restrict: string, scope: {ngModel: string, templateSelection: string, templateResult: string}, link: Function}}
     */
    function scSelect2($compile, $timeout) {
        return {
            restrict: 'A',
            scope: {
                ngModel: '=',
                templateSelection: '=',
                templateResult: '='
            },
            link: function ($scope, element, $attr) {
                if ($attr.language) {
                    var domElem = '<script src="/bower_components/select2/select2_locale_' + $attr.language + '.js" async defer></script>';
                    $(element).append($compile(domElem)($scope));
                }
                var options = _.pick($(element).data(), function (value, key) {
                    return !startsWith(key, '$');
                });
                options.formatSelection = $scope.templateSelection || $.fn.select2.defaults.formatSelection;
                options.formatResult = $scope.templateResult || $.fn.select2.defaults.formatResult;
                var selectorName = $attr.multiple ? 'multiselect' : 'select';
                var select = $('select.sc-' + selectorName + '[id="' + $attr.id + '"]');

                $timeout(function () {
                    select.select2(options);
                    updateSelectedItemsOnDisplay();

                    $scope.$watch( 'ngModel', function() {
                        updateSelectedItemsOnDisplay();
                    });
                });

                // Access ngModel, $attr.multiple, select, options.value,
                function updateSelectedItemsOnDisplay() {
                    if (!$scope.ngModel) return;

                    //True for both single and multiselect
                    if ($scope.ngModel.then && typeof $scope.ngModel.then === 'function') {
                        $scope.ngModel.then(function (response) {
                            //Multi select can have 1 or several default selected options,use each to initialize the select
                            //Single select has 1 default selected option, no iteration is needed to initialize the select
                            var selectedItems = $attr.multiple ? response.data : new Array(response.data);
                            populatePreselectedOptions(select, selectedItems, options.value);
                        });
                    }
                    else {
                        if (!_.isArray($scope.ngModel) && !_.isObject($scope.ngModel)) return;
                        var selectedItems = _.isArray($scope.ngModel) ? $scope.ngModel : new Array($scope.ngModel);
                        populatePreselectedOptions(select, selectedItems, options.value);
                    }
                };

            }
        };

        function populatePreselectedOptions(scSelect, selectedItems, key) {
            var selectedOptions = [];
            _.each(selectedItems, function (selectedItem) {
                var selectedId = selectedItem[key];
                var selectedOption = _.find(scSelect[0], function (option) {
                    return selectedId == option.value;
                });
                selectedOptions.push({id: selectedId, text: selectedOption.label});
            });
            if (selectedItems.length == 1) selectedOptions = selectedOptions.pop();
            scSelect.select2('data', selectedOptions);
        }

        function startsWith(str, target) {
            return str.indexOf(target) === 0;
        }
    }

})();
<commit_msg>bugfix resolve inner directives before compiling select2<commit_after>/**
 * scania-angular-ui
 * https://github.com/scania-bootstrap/scania-angular-ui
 * License: MIT
 *
 *
 */

(function () {
    'use strict';

    angular.module('scania.angular.ui', ['scania.angular.lightbox', 'scania.angular.select2']);

    /**
     * @ngdoc module
     * @name scania.angular.lightbox
     *
     * @description
     * Scania lightbox module
     */
    angular.module('scania.angular.lightbox', ['flow', 'ui.bootstrap']).directive('scLightbox', ['$scmodal', scLightbox]);
    /**
     /**
     * @ngdoc directive
     * @name scLightbox
     * @module scania.angular.lightbox
     *
     * @description file upload and gallery extension on angularJs framework
     * @param $scmodal
     * @returns {{restrict: string, templateUrl: string, controllerAs: string, controller: Function}}
     */

    function scLightbox($scmodal) {

        return {
            restrict: 'AEC',
            templateUrl: 'template/scania-angular-upload.html',
            controllerAs: 'lightbox',
            controller: function ($scope, $attrs) {
                $animate.enabled(false); // to solve the conflict between ngAnimate and ui-bootstrap animate
                var self = this;

                self.interval = $attrs.interval;
                self.open = function (index) {
                    $scope.slides = self.flow.files;
                    $scope.slides[index].active = true;
                    self.activeImage = $scope.slides[index];

                    self.modalInstance = $scmodal.open({
                        animation: $attrs.animation,
                        templateUrl: 'template/scania-angular-lightbox.html',
                        size: $attrs.size,
                        windowClass: $attrs.windowclass,
                        scope: $scope,
                        resolve: {
                            slides: function () {
                                return $scope.slides;
                            }
                        },
                        controller: function ($scope) {
                            $scope.setAsActive = function (slide) {
                                if (slide.active) {
                                    self.activeImage = slide;
                                }
                            };
                        }
                    });
                };
                self.deleteImage = function () {
                    self.activeImage.cancel();
                    if ($scope.slides.length === 0) {
                        self.modalInstance.dismiss();
                    }
                };
            }
        };
    }

    /**
     * @ngdoc module
     * @name scania.angular.select2
     *
     * @description
     * Scania select2 directive module
     */
    angular.module('scania.angular.select2', []).directive('scSelect2', ['$compile', '$timeout', scSelect2]);

    /**
     * @ngdoc directive
     * @name scLightbox
     * @module scania.angular.select2
     *
     * @description AngularJS directive for Select2
     * @param $compile
     * @param $timeout
     * @returns {{restrict: string, scope: {ngModel: string, templateSelection: string, templateResult: string}, link: Function}}
     */
    function scSelect2($compile, $timeout) {
        return {
            restrict: 'A',
            scope: {
                ngModel: '=',
                templateSelection: '=',
                templateResult: '='
            },
            link: function ($scope, element, $attr) {
                if ($attr.language) {
                    var domElem = '<script src="/bower_components/select2/select2_locale_' + $attr.language + '.js" async defer></script>';
                    $(element).append($compile(domElem)($scope));
                }
                var options = _.pick($(element).data(), function (value, key) {
                    return !startsWith(key, '$');
                });
                options.formatSelection = $scope.templateSelection || $.fn.select2.defaults.formatSelection;
                options.formatResult = $scope.templateResult || $.fn.select2.defaults.formatResult;
                var selectorName = $attr.multiple ? 'multiselect' : 'select',
                    select = {};

                $timeout(function () {
                    select = $('select.sc-' + selectorName + '[id="' + $attr.id + '"]');
                    select.select2(options);
                    updateSelectedItemsOnDisplay();

                    $scope.$watch( 'ngModel', function() {
                        updateSelectedItemsOnDisplay();
                    });
                });

                // Access ngModel, $attr.multiple, select, options.value,
                function updateSelectedItemsOnDisplay() {
                    if (!$scope.ngModel) return;

                    //True for both single and multiselect
                    if ($scope.ngModel.then && typeof $scope.ngModel.then === 'function') {
                        $scope.ngModel.then(function (response) {
                            //Multi select can have 1 or several default selected options,use each to initialize the select
                            //Single select has 1 default selected option, no iteration is needed to initialize the select
                            var selectedItems = $attr.multiple ? response.data : new Array(response.data);
                            populatePreselectedOptions(select, selectedItems, options.value);
                        });
                    }
                    else {
                        if (!_.isArray($scope.ngModel) && !_.isObject($scope.ngModel)) return;
                        var selectedItems = _.isArray($scope.ngModel) ? $scope.ngModel : new Array($scope.ngModel);
                        populatePreselectedOptions(select, selectedItems, options.value);
                    }
                };

            }
        };

        function populatePreselectedOptions(scSelect, selectedItems, key) {
            var selectedOptions = [];
            _.each(selectedItems, function (selectedItem) {
                var selectedId = selectedItem[key];
                var selectedOption = _.find(scSelect[0], function (option) {
                    return selectedId == option.value;
                });
                if(!selectedOption) {
                    console.error("Data-value for " + scSelect[0].id +" must have the same value as its track by.");
                    return;
                }
                selectedOptions.push({id: selectedId, text: selectedOption.label});

            });
            if (selectedItems.length == 1) selectedOptions = selectedOptions.pop();
            scSelect.select2('data', selectedOptions);
        }

        function startsWith(str, target) {
            return str.indexOf(target) === 0;
        }
    }

})();
<|endoftext|>