<commit_before>/* Timetable for public transport in Munich */

/*
 * Magic Mirror
 * Module: MVG Munich
 *
 * By Simon Crnko
 * MIT Licensed
 *
 */

const MS_PER_MINUTE = 60000;
Module.register("mvgmunich", {
	// Default module configuration
	defaults: {
		maxEntries: 8, // maximum number of results shown on UI
		updateInterval: MS_PER_MINUTE, // update every 60 seconds
		haltestelle: "Hauptbahnhof", // default departure station
		haltestelleId: 0,
		haltestelleName: "",
		ignoreStations: [], // list of destination to be ignored in the list
		lineFiltering: {
			"active": true, 			// set this to active if filtering should be used
			"filterType": "whitelist", 	// whitelist = only specified lines will be displayed, blacklist = all lines except specified lines will be displayed
			"lineNumbers": ["U1, U3, X50"] // lines that should be on the white-/blacklist
		},
		timeToWalk: 0, 		// walking time to the station
		showWalkingTime: false, // if the walking time should be included and the starting time is displayed
		showTrainDepartureTime: true,
		trainDepartureTimeFormat: "relative",
		walkingTimeFormat: "relative",
		showIcons: true,
		transportTypesToShow: {
			"ubahn": true,
			"sbahn": true,
			"regional_bus": true,
			"bus": true,
			"tram": true
		},
		showInterruptions: false,
		showInterruptionsDetails: false,
		countInterruptionsAsItemShown: false,
	},

	getStyles: function () {
		return ["mvgmunich.css"];
	},

	// Load translations files
	getTranslations: function () {
		return {
			en: "translations/en.json",
			de: "translations/de.json"
		};
	},

	start: function () {
		this.resultData = [];
		this.interruptionData = null;
		Log.info("Starting module: " + this.name + ", identifier: " + this.identifier);
		if (this.config.haltestelle !== "") {
			this.sendSocketNotification("GET_STATION_INFO", this.config);
		}
	},

	/*
	 * getData
	 * function call getData function in node_helper.js
	 *
	 */
	getData: function () {
		const self = this;
		self.sendSocketNotification("GET_DEPARTURE_DATA", self.config);
		setInterval(function () {
			self.sendSocketNotification("GET_DEPARTURE_DATA", self.config);
		}, self.config.updateInterval);
	},

	// Override dom generator.
	getDom: function () {
		let wrapperTable = document.createElement("div");
		if (this.config.haltestelle === "") {
			wrapperTable.className = "dimmed light small";
			wrapperTable.innerHTML = "Please set value for 'Haltestelle'.";
			return wrapperTable;
		}

		if (this.resultData === []) {
			wrapperTable.className = "dimmed light small";
			wrapperTable.innerHTML = "Loading data from MVG ...";
			return wrapperTable;
		}
		wrapperTable = document.createElement("table");
		wrapperTable.className = "small";
		wrapperTable.innerHTML = this.resultData[this.config.haltestelle];
		return wrapperTable;
	},

	getHtml: function (jsonObject) {
		let htmlText = "";

		let visibleLines = 0;
		const interruptions = new Set();

		for (let i = 0; i < jsonObject.departures.length; i++) {
			if (visibleLines >= this.config.maxEntries) {
				break;
			}
			// get one item from api result
			const apiResultItem = jsonObject.departures[i];
			// get transport type
			const transportType = apiResultItem.product.toLocaleLowerCase();

			// check if we should show data of this transport type
			if (!this.config.transportTypesToShow[transportType]
				|| this.config.ignoreStations.includes(apiResultItem.destination)
				|| this.checkToIgnoreOrIncludeLine(apiResultItem.label)
			) {
				continue;
			}

			if (this.config.showInterruptions && this.isLineAffected(apiResultItem.label)) {
				htmlText += "
";
			} else {
				htmlText += "
";
			}
			// check if user want's icons
			htmlText += this.showIcons(apiResultItem.product, this.config.showIcons);
			// add transport number
			htmlText += "" + apiResultItem.label + "	";
			// add last station aka direction
			htmlText += "" + apiResultItem.destination + "	";
			// check if user want's to see departure time
			htmlText += this.showDepartureTime(apiResultItem.departureTime);
			// check if user want's to see walking time
			htmlText += this.showWalkingTime(apiResultItem.departureTime);
			htmlText += "
";
			if (this.config.showInterruptionsDetails && this.isLineAffected(apiResultItem.label)) {
				let interruption = this.getInterruptionsDetails(apiResultItem.label);
				if (!interruptions.has(interruption)) {
					interruptions.add(interruption);
					htmlText += "
	" + interruption +"
";
					if (this.config.countInterruptionsAsItemShown) {
						visibleLines++;
					}
				}
			}
			visibleLines++;
		}
		return htmlText;
	},

	checkToIgnoreOrIncludeLine: function (lineName) {
		return this.config.lineFiltering !== undefined
		&& this.config.lineFiltering.active
		&& (this.config.lineFiltering.filterType.localeCompare("whitelist") === 0 ?
				!this.checkLineNumbersIncludes(lineName) : this.checkLineNumbersIncludes(lineName));
	},

	checkLineNumbersIncludes: function (lineName) {
		return (this.config.lineFiltering.lineNumbers.includes(lineName));
	},

	isLineAffected: function (lineName) {
		for (let i = 0; i < this.interruptionData.affectedLines.line.length; i++) {
			if (this.interruptionData.affectedLines.line[i].line === lineName) {
				return true;
			}
		}
		return false;
	},

	getInterruptionsDetails: function (lineName) {
		for (let i = 0; i < this.interruptionData.interruption.length; i++) {
			if (this.interruptionData.interruption[i].lines.line != null) {
				for (let j = 0; j < this.interruptionData.interruption[i].lines.line.length; j++) {
					if (this.interruptionData.interruption[i].lines.line[j].line === lineName) {
						return this.interruptionData.interruption[i].duration.text + " - " + this.interruptionData.interruption[i].title;
					}
				}
			}
   		}
		return "";
	},

	showIcons: function (product, showIcons) {
		let icons = "";
		if (showIcons) {
			icons = "	";
		}
		return icons;
	}
	,

	showWalkingTime: function (departureTime) {
		let htmlText = "";
		if (this.config.showWalkingTime) {
			htmlText += " / ";
			const startWalkingTime = new Date(departureTime - this.config.timeToWalk * MS_PER_MINUTE);
			// check what kind of walking time user wants (absolute / relative)
			if (this.config.walkingTimeFormat === "absolute") {
				htmlText += this.getAbsoluteTime(startWalkingTime);
			} else if (this.config.walkingTimeFormat === "relative") {
				htmlText += this.getRelativeTime(startWalkingTime);
			} else {
				htmlText += "walkingTimeFormat config is wrong";
			}
			htmlText += "	";
		}
		return htmlText;
	}
	,

	showDepartureTime: function (departureTime) {
		let htmlText = "";
		if (this.config.showTrainDepartureTime) {
			// add departure time
			htmlText += "";
			const departureDate = new Date(departureTime);
			// check what kind of time user wants (absolute / relative)
			if (this.config.trainDepartureTimeFormat === "absolute") {
				htmlText += this.getAbsoluteTime(departureDate);
			} else if (this.config.trainDepartureTimeFormat === "relative") {
				htmlText += this.getRelativeTime(departureDate);
			} else {
				htmlText += "trainDepartureTimeFormat config is wrong";
			}
			htmlText += "	";
		}
		return htmlText;
	}
	,

	getAbsoluteTime: function (time) {
		let hoursStr = (time.getHours() < 10 ? "0" : "") + time.getHours();
		let minutesStr = (time.getMinutes() < 10 ? "0" : "") + time.getMinutes();

		return hoursStr + ":" + minutesStr;
	}
	,

	getRelativeTime: function (time) {
		const timingForStartWalking = Math.floor((time.getTime() - new Date().getTime()) / 1000 / 60);
		return (timingForStartWalking <= 0
			? this.translate("JETZT")
			: this.translate("IN") + " " + timingForStartWalking + " " + this.translate("MIN"));
	}
	,

	// Override getHeader method.
	getHeader: function () {
		if (this.config.haltestelle !== "" || this.config.haltestelleName !== "") {
			return this.data.header + " Munich: " +
				(this.config.haltestelleName === "" ? this.config.haltestelle : this.config.haltestelleName);
		}
		return "";
	}
	,

	socketNotificationReceived: function (notification, payload) {
		switch (notification) {
		case "UPDATE_DEPARTURE_INFO":
			this.resultData[payload.haltestelle] = this.getHtml(payload.transport);
			break;

		case "UPDATE_STATION":
			if (this.config.haltestelle === payload.haltestelle) {
				this.config.haltestelleId = payload.haltestelleId;
				this.config.haltestelleName = payload.haltestelleName;
			}
			this.getHeader();
			this.getData();
			break;

		case "UPDATE_INTERRUPTION_DATA":
			this.interruptionData = payload;
			break;

		default:
			Log.error();
		}
		this.updateDom();
	}
});
<commit_msg>isLineAffected function crashes if .affectedLines is undefined fixed<commit_after>/* Timetable for public transport in Munich */

/*
 * Magic Mirror
 * Module: MVG Munich
 *
 * By Simon Crnko
 * MIT Licensed
 *
 */

const MS_PER_MINUTE = 60000;
Module.register("mvgmunich", {
	// Default module configuration
	defaults: {
		maxEntries: 8, // maximum number of results shown on UI
		updateInterval: MS_PER_MINUTE, // update every 60 seconds
		haltestelle: "Hauptbahnhof", // default departure station
		haltestelleId: 0,
		haltestelleName: "",
		ignoreStations: [], // list of destination to be ignored in the list
		lineFiltering: {
			"active": true, 			// set this to active if filtering should be used
			"filterType": "whitelist", 	// whitelist = only specified lines will be displayed, blacklist = all lines except specified lines will be displayed
			"lineNumbers": ["U1, U3, X50"] // lines that should be on the white-/blacklist
		},
		timeToWalk: 0, 		// walking time to the station
		showWalkingTime: false, // if the walking time should be included and the starting time is displayed
		showTrainDepartureTime: true,
		trainDepartureTimeFormat: "relative",
		walkingTimeFormat: "relative",
		showIcons: true,
		transportTypesToShow: {
			"ubahn": true,
			"sbahn": true,
			"regional_bus": true,
			"bus": true,
			"tram": true
		},
		showInterruptions: false,
		showInterruptionsDetails: false,
		countInterruptionsAsItemShown: false,
	},

	getStyles: function () {
		return ["mvgmunich.css"];
	},

	// Load translations files
	getTranslations: function () {
		return {
			en: "translations/en.json",
			de: "translations/de.json"
		};
	},

	start: function () {
		this.resultData = [];
		this.interruptionData = null;
		Log.info("Starting module: " + this.name + ", identifier: " + this.identifier);
		if (this.config.haltestelle !== "") {
			this.sendSocketNotification("GET_STATION_INFO", this.config);
		}
	},

	/*
	 * getData
	 * function call getData function in node_helper.js
	 *
	 */
	getData: function () {
		const self = this;
		self.sendSocketNotification("GET_DEPARTURE_DATA", self.config);
		setInterval(function () {
			self.sendSocketNotification("GET_DEPARTURE_DATA", self.config);
		}, self.config.updateInterval);
	},

	// Override dom generator.
	getDom: function () {
		let wrapperTable = document.createElement("div");
		if (this.config.haltestelle === "") {
			wrapperTable.className = "dimmed light small";
			wrapperTable.innerHTML = "Please set value for 'Haltestelle'.";
			return wrapperTable;
		}

		if (this.resultData === []) {
			wrapperTable.className = "dimmed light small";
			wrapperTable.innerHTML = "Loading data from MVG ...";
			return wrapperTable;
		}
		wrapperTable = document.createElement("table");
		wrapperTable.className = "small";
		wrapperTable.innerHTML = this.resultData[this.config.haltestelle];
		return wrapperTable;
	},

	getHtml: function (jsonObject) {
		let htmlText = "";

		let visibleLines = 0;
		const interruptions = new Set();

		for (let i = 0; i < jsonObject.departures.length; i++) {
			if (visibleLines >= this.config.maxEntries) {
				break;
			}
			// get one item from api result
			const apiResultItem = jsonObject.departures[i];
			// get transport type
			const transportType = apiResultItem.product.toLocaleLowerCase();

			// check if we should show data of this transport type
			if (!this.config.transportTypesToShow[transportType]
				|| this.config.ignoreStations.includes(apiResultItem.destination)
				|| this.checkToIgnoreOrIncludeLine(apiResultItem.label)
			) {
				continue;
			}

			if (this.config.showInterruptions && this.isLineAffected(apiResultItem.label)) {
				htmlText += "
";
			} else {
				htmlText += "
";
			}
			// check if user want's icons
			htmlText += this.showIcons(apiResultItem.product, this.config.showIcons);
			// add transport number
			htmlText += "" + apiResultItem.label + "	";
			// add last station aka direction
			htmlText += "" + apiResultItem.destination + "	";
			// check if user want's to see departure time
			htmlText += this.showDepartureTime(apiResultItem.departureTime);
			// check if user want's to see walking time
			htmlText += this.showWalkingTime(apiResultItem.departureTime);
			htmlText += "
";
			if (this.config.showInterruptionsDetails && this.isLineAffected(apiResultItem.label)) {
				let interruption = this.getInterruptionsDetails(apiResultItem.label);
				if (!interruptions.has(interruption)) {
					interruptions.add(interruption);
					htmlText += "
	" + interruption +"
";
					if (this.config.countInterruptionsAsItemShown) {
						visibleLines++;
					}
				}
			}
			visibleLines++;
		}
		return htmlText;
	},

	checkToIgnoreOrIncludeLine: function (lineName) {
		return this.config.lineFiltering !== undefined
		&& this.config.lineFiltering.active
		&& (this.config.lineFiltering.filterType.localeCompare("whitelist") === 0 ?
				!this.checkLineNumbersIncludes(lineName) : this.checkLineNumbersIncludes(lineName));
	},

	checkLineNumbersIncludes: function (lineName) {
		return (this.config.lineFiltering.lineNumbers.includes(lineName));
	},

	isLineAffected: function (lineName) {
		if (this.interruptionData.affectedLines != undefined) {
			for (let i = 0; i < this.interruptionData.affectedLines.line.length; i++) {
				if (this.interruptionData.affectedLines.line[i].line === lineName) {
					return true;
				}
			}
		}
		return false;
	},

	getInterruptionsDetails: function (lineName) {
		for (let i = 0; i < this.interruptionData.interruption.length; i++) {
			if (this.interruptionData.interruption[i].lines.line != null) {
				for (let j = 0; j < this.interruptionData.interruption[i].lines.line.length; j++) {
					if (this.interruptionData.interruption[i].lines.line[j].line === lineName) {
						return this.interruptionData.interruption[i].duration.text + " - " + this.interruptionData.interruption[i].title;
					}
				}
			}
   		}
		return "";
	},

	showIcons: function (product, showIcons) {
		let icons = "";
		if (showIcons) {
			icons = "	";
		}
		return icons;
	}
	,

	showWalkingTime: function (departureTime) {
		let htmlText = "";
		if (this.config.showWalkingTime) {
			htmlText += " / ";
			const startWalkingTime = new Date(departureTime - this.config.timeToWalk * MS_PER_MINUTE);
			// check what kind of walking time user wants (absolute / relative)
			if (this.config.walkingTimeFormat === "absolute") {
				htmlText += this.getAbsoluteTime(startWalkingTime);
			} else if (this.config.walkingTimeFormat === "relative") {
				htmlText += this.getRelativeTime(startWalkingTime);
			} else {
				htmlText += "walkingTimeFormat config is wrong";
			}
			htmlText += "	";
		}
		return htmlText;
	}
	,

	showDepartureTime: function (departureTime) {
		let htmlText = "";
		if (this.config.showTrainDepartureTime) {
			// add departure time
			htmlText += "";
			const departureDate = new Date(departureTime);
			// check what kind of time user wants (absolute / relative)
			if (this.config.trainDepartureTimeFormat === "absolute") {
				htmlText += this.getAbsoluteTime(departureDate);
			} else if (this.config.trainDepartureTimeFormat === "relative") {
				htmlText += this.getRelativeTime(departureDate);
			} else {
				htmlText += "trainDepartureTimeFormat config is wrong";
			}
			htmlText += "	";
		}
		return htmlText;
	}
	,

	getAbsoluteTime: function (time) {
		let hoursStr = (time.getHours() < 10 ? "0" : "") + time.getHours();
		let minutesStr = (time.getMinutes() < 10 ? "0" : "") + time.getMinutes();

		return hoursStr + ":" + minutesStr;
	}
	,

	getRelativeTime: function (time) {
		const timingForStartWalking = Math.floor((time.getTime() - new Date().getTime()) / 1000 / 60);
		return (timingForStartWalking <= 0
			? this.translate("JETZT")
			: this.translate("IN") + " " + timingForStartWalking + " " + this.translate("MIN"));
	}
	,

	// Override getHeader method.
	getHeader: function () {
		if (this.config.haltestelle !== "" || this.config.haltestelleName !== "") {
			return this.data.header + " Munich: " +
				(this.config.haltestelleName === "" ? this.config.haltestelle : this.config.haltestelleName);
		}
		return "";
	}
	,

	socketNotificationReceived: function (notification, payload) {
		switch (notification) {
		case "UPDATE_DEPARTURE_INFO":
			this.resultData[payload.haltestelle] = this.getHtml(payload.transport);
			break;

		case "UPDATE_STATION":
			if (this.config.haltestelle === payload.haltestelle) {
				this.config.haltestelleId = payload.haltestelleId;
				this.config.haltestelleName = payload.haltestelleName;
			}
			this.getHeader();
			this.getData();
			break;

		case "UPDATE_INTERRUPTION_DATA":
			this.interruptionData = payload;
			break;

		default:
			Log.error();
		}
		this.updateDom();
	}
});
<|endoftext|>


<commit_before>/**
 * The Studio: Artist of the Day plugin
 * This is a daily activity where users nominate the featured artist for the day, which is selected randomly once voting has ended.
 * Only works in a room with the id 'thestudio'
 */

function toArtistId(artist) { // toId would return '' for foreign/sadistic artists
	return artist.toLowerCase().replace(/\s/g, '').replace(/\b&\b/g, '');
}

var artistOfTheDay = {
	pendingNominations: false,
	nominations: new Map(),
	removedNominators: []
};

var theStudio = Rooms.get('thestudio');
if (theStudio && !theStudio.plugin) {
	theStudio.plugin = artistOfTheDay;
}

var commands = {
	start: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.can('mute', null, room)) return false;
		if (artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are already in progress.');

		var nominations = artistOfTheDay.nominations;
		var prenominations = room.chatRoomData.prenominations;
		if (prenominations && prenominations.length) {
			for (var i = prenominations.length; i--;) {
				var prenomination = prenominations[i];
				nominations.set(Users.get(prenomination[0].userid) || prenomination[0], prenomination[1]);
			}
		}

		artistOfTheDay.pendingNominations = true;
		room.chatRoomData.prenominations = [];
		Rooms.global.writeChatRoomData();
		room.addRaw('
Nominations for the Artist of the Day have begun!
' +
		            'Use /aotd nom to nominate an artist.
');
		this.privateModCommand('(' + user.name + ' began nominations for the Artist of the Day.)');
	},

	end: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.can('mute', null, room)) return false;
		if (!artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are not in progress.');
		if (!artistOfTheDay.nominations.size) return this.sendReply('No nominations have been submitted yet.');

		var nominations = Array.from(artistOfTheDay.nominations.values());
		var artist = nominations[~~Math.random(nominations.length)];
		artistOfTheDay.pendingNominations = false;
		artistOfTheDay.nominations.clear();
		artistOfTheDay.removedNominators = [];
		room.chatRoomData.artistOfTheDay = artist;
		Rooms.global.writeChatRoomData();
		room.addRaw('
Nominations for the Artist of the Day have ended!
' +
		            'Selected artist: ' + Tools.escapeHTML(artist) + '
');
		this.privateModCommand('(' + user.name + ' ended nominations for the Artist of the Day.)');
	},

	prenom: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !target) return false;
		if (artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are in progress.');
		if (!room.chatRoomData.prenominations) room.chatRoomData.prenominations = [];

		var userid = user.userid;
		var ips = user.ips;
		var prenominationId = toArtistId(target);
		if (!prenominationId) return this.sendReply('' + target + ' is not a valid artist name.');
		if (toArtistId(room.chatRoomData.artistOfTheDay) === prenominationId) return this.sendReply('' + target + ' is already the current Artist of the Day.');

		var prenominations = room.chatRoomData.prenominations;
		var prenominationIndex = -1;
		var latestIp = user.latestIp;
		for (var i = prenominations.length; i--;) {
			if (toArtistId(prenominations[i][1]) === prenominationId) return this.sendReply('' + target + ' has already been prenominated.');

			if (prenominationIndex < 0) {
				var prenominator = prenominations[i][0];
				if (prenominator.userid === userid || prenominator.ips[latestIp]) prenominationIndex = i;
			}
		}

		if (prenominationIndex > -1) {
			prenominations[prenominationIndex][1] = target;
			Rooms.global.writeChatRoomData();
			return this.sendReply('Your prenomination was changed to ' + target + '.');
		}

		prenominations.push([{name: user.name, userid: userid, ips: user.ips}, target]);
		Rooms.global.writeChatRoomData();
		this.sendReply('' + target + ' was submitted for the next nomination period for the Artist of the Day.');
	},

	nom: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData) return false;
		if (!artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are not in progress.');

		var removedNominators = artistOfTheDay.removedNominators;
		if (removedNominators.indexOf(user) > -1) return this.sendReply('Since your nomination has been removed, you cannot submit another artist until the next round.');

		var alts = user.getAlts();
		for (var i = removedNominators.length; i--;) {
			if (alts.indexOf(removedNominators[i].name) > -1) return this.sendReply('Since your nomination has been removed, you cannot submit another artist until the next round.');
		}

		var nominationId = toArtistId(target);
		if (toArtistId(room.chatRoomData.artistOfTheDay) === nominationId) return this.sendReply('' + target + ' was the last Artist of the Day.');

		var userid = user.userid;
		var latestIp = user.latestIp;
		for (var data, nominationsIterator = artistOfTheDay.nominations.entries(); !!(data = nominationsIterator.next().value);) { // replace with for-of loop once available
			var nominator = data[0];
			if (nominator.ips[latestIp] && nominator.userid !== userid || alts.indexOf(nominator.name) > -1) return this.sendReply('You have already submitted a nomination for the Artist of the Day under the name ' + nominator.name + '.');
			if (toArtistId(data[1]) === nominationId) return this.sendReply('' + target + ' has already been nominated.');
		}

		artistOfTheDay.nominations.set(user, target);
		this.send('' + user.name + ' nominated ' + target + ' for the Artist of the Day.');
	},

	viewnoms: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData) return false;

		var buffer = '';
		if (!artistOfTheDay.pendingNominations) {
			if (!user.can('mute', null, room)) return false;

			var prenominations = room.chatRoomData.prenominations;
			if (!prenominations || !prenominations.length) return this.sendReplyBox('No prenominations have been submitted yet.');

			var i = prenominations.length;
			buffer += 'Current prenominations:';
			while (i--) {
				buffer += '
- ' + Tools.escapeHTML(prenominations[i][1]) + ' (submitted by ' + Tools.escapeHTML(prenominations[i][0].name) + ')';
			}
			return this.sendReplyBox(buffer);
		}

		if (!artistOfTheDay.nominations.size) return this.sendReplyBox('No nominations have been submitted yet.');

		var nominations = Array.from(artistOfTheDay.nominations.entries()).sort(function (a, b) {
			if (a[1]  b[1]) return -1;
			return 0;
		});
		var i = nominations.length;
		buffer += 'Current nominations:';
		while (i--) {
			buffer += '
- ' + Tools.escapeHTML(nominations[i][1]) + ' (submitted by ' + Tools.escapeHTML(nominations[i][0].name) + ')';
		}
		this.sendReplyBox(buffer);
	},

	removenom: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !target || !this.can('mute', null, room)) return false;
		if (!artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are not in progress.');
		if (!artistOfTheDay.nominations.size) return this.sendReply('No nominations have been submitted yet.');

		target = this.splitTarget(target);
		var name = this.targetUsername;
		var userid = toId(name);
		if (!userid) return this.sendReply('"' + name + '" is not a valid username.');

		for (var nominator, nominatorsIterator = artistOfTheDay.nominations.keys(); !!(nominator = nominatorsIterator.next().value);) { // replace with for-of loop once available
			if (nominator.userid === userid) {
				artistOfTheDay.nominations.delete(nominator);
				artistOfTheDay.removedNominators.push(nominator);
				return this.privateModCommand('(' + user.name + ' removed ' + nominator.name + '\'s nomination for the Artist of the Day.)');
			}
		}

		this.sendReply('User "' + name + '" has no nomination for the Artist of the Day.');
	},

	set: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.can('mute', null, room)) return false;
		if (!toId(target)) return this.sendReply('No valid artist was specified.');
		if (artistOfTheDay.pendingNominations) return this.sendReply('The Artist of the Day cannot be set while nominations are in progress.');

		room.chatRoomData.artistOfTheDay = target;
		Rooms.global.writeChatRoomData();
		this.privateModCommand('(' + user.name + ' set the Artist of the Day to ' + target + '.)');
	},

	'': function (target, room) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.canBroadcast()) return false;
		this.sendReplyBox('The Artist of the Day ' + (room.chatRoomData.artistOfTheDay ? 'is ' + room.chatRoomData.artistOfTheDay + '.' : 'has not been set yet.'));
	},

	help: function (target, room) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.canBroadcast()) return false;
		this.sendReplyBox('The Studio: Artist of the Day plugin commands:
' +
		                  '- /aotd - View the Artist of the Day.
' +
				  '- /aotd start - Start nominations for the Artist of the Day. Requires: % @ # & ~
' +
				  '- /aotd nom - Nominate an artist for the Artist of the Day.
' +
				  '- /aotd viewnoms - View the current nominations for the Artist of the Day. Requires: % @ # & ~
' +
				  '- /aotd removenom [username] - Remove a user\'s nomination for the Artist of the Day and prevent them from voting again until the next round. Requires: % @ # & ~
' +
				  '- /aotd end - End nominations for the Artist of the Day and set it to a randomly selected artist. Requires: % @ # & ~
' +
				  '- /aotd prenom - Nominate an artist for the Artist of the Day between nomination periods.
' +
				  '- /aotd set [artist] - Set the Artist of the Day. Requires: % @ # & ~');
	}
};

exports.commands = {
	aotd: commands
};
<commit_msg>The Studio plugin: separate response for changing nominations<commit_after>/**
 * The Studio: Artist of the Day plugin
 * This is a daily activity where users nominate the featured artist for the day, which is selected randomly once voting has ended.
 * Only works in a room with the id 'thestudio'
 */

function toArtistId(artist) { // toId would return '' for foreign/sadistic artists
	return artist.toLowerCase().replace(/\s/g, '').replace(/\b&\b/g, '');
}

var artistOfTheDay = {
	pendingNominations: false,
	nominations: new Map(),
	removedNominators: []
};

var theStudio = Rooms.get('thestudio');
if (theStudio && !theStudio.plugin) {
	theStudio.plugin = artistOfTheDay;
}

var commands = {
	start: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.can('mute', null, room)) return false;
		if (artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are already in progress.');

		var nominations = artistOfTheDay.nominations;
		var prenominations = room.chatRoomData.prenominations;
		if (prenominations && prenominations.length) {
			for (var i = prenominations.length; i--;) {
				var prenomination = prenominations[i];
				nominations.set(Users.get(prenomination[0].userid) || prenomination[0], prenomination[1]);
			}
		}

		artistOfTheDay.pendingNominations = true;
		room.chatRoomData.prenominations = [];
		Rooms.global.writeChatRoomData();
		room.addRaw('
Nominations for the Artist of the Day have begun!
' +
		            'Use /aotd nom to nominate an artist.
');
		this.privateModCommand('(' + user.name + ' began nominations for the Artist of the Day.)');
	},

	end: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.can('mute', null, room)) return false;
		if (!artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are not in progress.');
		if (!artistOfTheDay.nominations.size) return this.sendReply('No nominations have been submitted yet.');

		var nominations = Array.from(artistOfTheDay.nominations.values());
		var artist = nominations[~~Math.random(nominations.length)];
		artistOfTheDay.pendingNominations = false;
		artistOfTheDay.nominations.clear();
		artistOfTheDay.removedNominators = [];
		room.chatRoomData.artistOfTheDay = artist;
		Rooms.global.writeChatRoomData();
		room.addRaw('
Nominations for the Artist of the Day have ended!
' +
		            'Randomly selected artist: ' + Tools.escapeHTML(artist) + '
');
		this.privateModCommand('(' + user.name + ' ended nominations for the Artist of the Day.)');
	},

	prenom: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !target) return false;
		if (artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are in progress.');
		if (!room.chatRoomData.prenominations) room.chatRoomData.prenominations = [];

		var userid = user.userid;
		var ips = user.ips;
		var prenominationId = toArtistId(target);
		if (!prenominationId) return this.sendReply('' + target + ' is not a valid artist name.');
		if (toArtistId(room.chatRoomData.artistOfTheDay) === prenominationId) return this.sendReply('' + target + ' is already the current Artist of the Day.');

		var prenominations = room.chatRoomData.prenominations;
		var prenominationIndex = -1;
		var latestIp = user.latestIp;
		for (var i = prenominations.length; i--;) {
			if (toArtistId(prenominations[i][1]) === prenominationId) return this.sendReply('' + target + ' has already been prenominated.');

			if (prenominationIndex < 0) {
				var prenominator = prenominations[i][0];
				if (prenominator.userid === userid || prenominator.ips[latestIp]) prenominationIndex = i;
			}
		}

		if (prenominationIndex > -1) {
			prenominations[prenominationIndex][1] = target;
			Rooms.global.writeChatRoomData();
			return this.sendReply('Your prenomination was changed to ' + target + '.');
		}

		prenominations.push([{name: user.name, userid: userid, ips: user.ips}, target]);
		Rooms.global.writeChatRoomData();
		this.sendReply('' + target + ' was submitted for the next nomination period for the Artist of the Day.');
	},

	nom: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData) return false;
		if (!artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are not in progress.');

		var removedNominators = artistOfTheDay.removedNominators;
		if (removedNominators.indexOf(user) > -1) return this.sendReply('Since your nomination has been removed, you cannot submit another artist until the next round.');

		var alts = user.getAlts();
		for (var i = removedNominators.length; i--;) {
			if (alts.indexOf(removedNominators[i].name) > -1) return this.sendReply('Since your nomination has been removed, you cannot submit another artist until the next round.');
		}

		var nominationId = toArtistId(target);
		if (toArtistId(room.chatRoomData.artistOfTheDay) === nominationId) return this.sendReply('' + target + ' was the last Artist of the Day.');

		var userid = user.userid;
		var latestIp = user.latestIp;
		for (var data, nominationsIterator = artistOfTheDay.nominations.entries(); !!(data = nominationsIterator.next().value);) { // replace with for-of loop once available
			var nominator = data[0];
			if (nominator.ips[latestIp] && nominator.userid !== userid || alts.indexOf(nominator.name) > -1) return this.sendReply('You have already submitted a nomination for the Artist of the Day under the name ' + nominator.name + '.');
			if (toArtistId(data[1]) === nominationId) return this.sendReply('' + target + ' has already been nominated.');
		}

		var response = '' + user.name + (artistOfTheDay.nominations.has(user) ? ' changed their nomination from ' + artistOfTheDay.nominations.get(user) + ' to ' + target + '.' : ' nominated ' + target + ' for the Artist of the Day.');
		artistOfTheDay.nominations.set(user, target);
		this.send(response);
	},

	viewnoms: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData) return false;

		var buffer = '';
		if (!artistOfTheDay.pendingNominations) {
			if (!user.can('mute', null, room)) return false;

			var prenominations = room.chatRoomData.prenominations;
			if (!prenominations || !prenominations.length) return this.sendReplyBox('No prenominations have been submitted yet.');

			var i = prenominations.length;
			buffer += 'Current prenominations:';
			while (i--) {
				buffer += '
- ' + Tools.escapeHTML(prenominations[i][1]) + ' (submitted by ' + Tools.escapeHTML(prenominations[i][0].name) + ')';
			}
			return this.sendReplyBox(buffer);
		}

		if (!this.canBroadcast()) return false;
		if (!artistOfTheDay.nominations.size) return this.sendReplyBox('No nominations have been submitted yet.');

		var nominations = Array.from(artistOfTheDay.nominations.entries()).sort(function (a, b) {
			if (a[1]  b[1]) return -1;
			return 0;
		});
		var i = nominations.length;
		buffer += 'Current nominations:';
		while (i--) {
			buffer += '
- ' + Tools.escapeHTML(nominations[i][1]) + ' (submitted by ' + Tools.escapeHTML(nominations[i][0].name) + ')';
		}
		this.sendReplyBox(buffer);
	},

	removenom: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !target || !this.can('mute', null, room)) return false;
		if (!artistOfTheDay.pendingNominations) return this.sendReply('Nominations for the Artist of the Day are not in progress.');
		if (!artistOfTheDay.nominations.size) return this.sendReply('No nominations have been submitted yet.');

		target = this.splitTarget(target);
		var name = this.targetUsername;
		var userid = toId(name);
		if (!userid) return this.sendReply('"' + name + '" is not a valid username.');

		for (var nominator, nominatorsIterator = artistOfTheDay.nominations.keys(); !!(nominator = nominatorsIterator.next().value);) { // replace with for-of loop once available
			if (nominator.userid === userid) {
				artistOfTheDay.nominations.delete(nominator);
				artistOfTheDay.removedNominators.push(nominator);
				return this.privateModCommand('(' + user.name + ' removed ' + nominator.name + '\'s nomination for the Artist of the Day.)');
			}
		}

		this.sendReply('User "' + name + '" has no nomination for the Artist of the Day.');
	},

	set: function (target, room, user) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.can('mute', null, room)) return false;
		if (!toId(target)) return this.sendReply('No valid artist was specified.');
		if (artistOfTheDay.pendingNominations) return this.sendReply('The Artist of the Day cannot be set while nominations are in progress.');

		room.chatRoomData.artistOfTheDay = target;
		Rooms.global.writeChatRoomData();
		this.privateModCommand('(' + user.name + ' set the Artist of the Day to ' + target + '.)');
	},

	'': function (target, room) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.canBroadcast()) return false;
		this.sendReplyBox('The Artist of the Day ' + (room.chatRoomData.artistOfTheDay ? 'is ' + room.chatRoomData.artistOfTheDay + '.' : 'has not been set yet.'));
	},

	help: function (target, room) {
		if (room.id !== 'thestudio' || !room.chatRoomData || !this.canBroadcast()) return false;
		this.sendReplyBox('The Studio: Artist of the Day plugin commands:
' +
		                  '- /aotd - View the Artist of the Day.
' +
				  '- /aotd start - Start nominations for the Artist of the Day. Requires: % @ # & ~
' +
				  '- /aotd nom - Nominate an artist for the Artist of the Day.
' +
				  '- /aotd viewnoms - View the current nominations for the Artist of the Day. Requires: % @ # & ~
' +
				  '- /aotd removenom [username] - Remove a user\'s nomination for the Artist of the Day and prevent them from voting again until the next round. Requires: % @ # & ~
' +
				  '- /aotd end - End nominations for the Artist of the Day and set it to a randomly selected artist. Requires: % @ # & ~
' +
				  '- /aotd prenom - Nominate an artist for the Artist of the Day between nomination periods.
' +
				  '- /aotd set [artist] - Set the Artist of the Day. Requires: % @ # & ~');
	}
};

exports.commands = {
	aotd: commands
};
<|endoftext|>


<commit_before>DV.PageSet = function(viewer){
  this.currentPage  = null;
  this.pages        = [];
  this.viewer       = viewer;
  this.zoomText();
};

// used to call the same method with the same params against all page instances
DV.PageSet.prototype.execute = function(action,params){
  this.pages.each(function(pageInstance){
    pageInstance[action].apply(pageInstance,params);
  });
};

// build the basic page presentation layer
DV.PageSet.prototype.buildPages = function(options) {
  options = options || {};
  var pages = this.getPages();
  for(var i = 0; i < pages.length; i++) {
    var page  = pages[i];
    page.set  = this;
    page.index = i;

    // TODO: Make more explicit, this is sloppy
    this.pages[page.label] = new DV.Page(this.viewer, page);

    if(page.currentPage == true) {
      this.currentPage = this.pages[page.label];
    }
  }
  this.viewer.models.annotations.renderAnnotations();
};

// used to generate references for the build action
DV.PageSet.prototype.getPages = function(){
  var _pages = [];

  this.viewer.elements.sets.each(function(_index,el){

    var currentPage = (_index == 0) ? true : false;
    _pages.push({ label: 'p'+_index, el: el, index: _index, pageNumber: _index+1, currentPage: currentPage });

  });

  return _pages;
};

// basic reflow to ensure zoomlevel is right, pages are in the right place and annotation limits are correct
DV.PageSet.prototype.reflowPages = function() {
  this.viewer.models.pages.resize();
  this.viewer.helpers.setActiveAnnotationLimits();
  this.redraw(false, true);
};

// reflow the pages without causing the container to resize or annotations to redraw
DV.PageSet.prototype.simpleReflowPages = function(){
  this.viewer.helpers.setActiveAnnotationLimits();
  this.redraw(false, false);
};

// hide any active annotations
DV.PageSet.prototype.cleanUp = function(){
  if(this.viewer.activeAnnotation){
    this.viewer.activeAnnotation.hide(true);
  }
};

DV.PageSet.prototype.zoom = function(argHash){
  if(this.viewer.models.document.zoomLevel === argHash.zoomLevel) return;

  var currentPage   = this.viewer.models.document.currentIndex();
  var _index      = currentPage - 1;
  var oldOffset     = this.viewer.models.document.offsets[currentPage];

  var oldZoom      = this.viewer.models.document.zoomLevel*1;
  var scrollPos    = this.viewer.elements.window.scrollTop();

  this.viewer.models.document.zoom(argHash.zoomLevel);

  var diff      = (parseInt(scrollPos, 10)>parseInt(oldOffset, 10)) ? scrollPos - oldOffset : oldOffset - scrollPos;

  var diffPercentage   = diff / this.viewer.models.pages.height;

  // this.position();
  this.reflowPages();
  this.zoomText();

  if(this.viewer.activeAnnotation != null){
    // FIXME:

    var args =
    {
      index: this.viewer.models.document.currentIndex(),
      top: this.viewer.activeAnnotation.y1,
      id: this.viewer.activeAnnotation.id
    };
    this.viewer.activeAnnotation = null;

    this.showAnnotation(args);
    this.viewer.helpers.setActiveAnnotationLimits(this.viewer.activeAnnotation);
  }else{
    var _offset      = Math.round(this.viewer.models.pages.height * diffPercentage);
    this.viewer.helpers.jump(this.viewer.models.document.currentIndex(),_offset);
  }
};

// Zoom the text container.
DV.PageSet.prototype.zoomText = function() {
  var padding = this.viewer.models.pages.DEFAULT_PADDING;
  var width   = this.viewer.models.pages.zoomLevel;
  this.viewer.$('.DV-textContents').width(width - padding);
  this.viewer.$('.DV-textPage').width(width);
  if (this.viewer.options.zoom == 'auto') {
    padding = this.viewer.models.pages.REDUCED_PADDING;
  }
  this.viewer.elements.collection.css({'width' : width + padding});
};

// draw the pages
DV.PageSet.prototype.draw = function(pageCollection){
  for(var i = 0, pageCollectionLength = pageCollection.length; i < pageCollectionLength;i++){
    var page = this.pages[pageCollection[i].label];
    if (page) page.draw({ index: pageCollection[i].index, pageNumber: pageCollection[i].index+1});
  }
};

DV.PageSet.prototype.redraw = function(stopResetOfPosition, redrawAnnotations) {
  var _index = this.viewer.models.document.currentIndex();
  if (this.pages['p0']) this.pages['p0'].draw({ force: true, forceAnnotationRedraw : redrawAnnotations });
  if (this.pages['p1']) this.pages['p1'].draw({ force: true, forceAnnotationRedraw : redrawAnnotations });
  if (this.pages['p2']) this.pages['p2'].draw({ force: true, forceAnnotationRedraw : redrawAnnotations });

  if(redrawAnnotations && this.viewer.activeAnnotation){
    this.viewer.helpers.jump(this.viewer.activeAnnotation.page.index,this.viewer.activeAnnotation.position.top - 37);
  }
};

// set the annotation to load ahead of time
DV.PageSet.prototype.setActiveAnnotation = function(annotationId, edit){
  this.viewer.annotationToLoadId   = annotationId;
  this.viewer.annotationToLoadEdit = edit ? annotationId : null;
};

// a funky fucking mess to jump to the annotation that is active
DV.PageSet.prototype.showAnnotation = function(argHash, showHash){
  showHash = showHash || {};

  // if state is ViewAnnotation, jump to the appropriate position in the view
  // else
  // hide active annotations and locate the position of the next annotation
  // NOTE: This needs work
  if(this.viewer.state === 'ViewAnnotation'){

    var offset = this.viewer.$('.DV-allAnnotations div[rel=aid-'+argHash.id+']')[0].offsetTop;
    this.viewer.elements.window.scrollTop(offset+10,'fast');
    this.viewer.helpers.setActiveAnnotationInNav(argHash.id);
    this.viewer.activeAnnotationId = argHash.id;
    this.viewer.history.save('annotation/a'+argHash.id);
    return;
  }else{
    this.viewer.helpers.removeObserver('trackAnnotation');
    this.viewer.activeAnnotationId = null;
    if(this.viewer.activeAnnotation != null){
      this.viewer.activeAnnotation.hide();
    }
    this.setActiveAnnotation(argHash.id, showHash.edit);

    var isPage = this.viewer.models.annotations.byId[argHash.id].type == 'page';
    var nudge  = isPage ? -7 : 36;
    var offset = (argHash.top * this.viewer.models.pages.zoomFactor()) - nudge;

    for(var i = 0; i <= 2; i++){
      if (this.pages['p' + i]) {
        for(var n = 0; n < this.pages['p'+i].annotations.length; n++){
          if(this.pages['p'+i].annotations[n].id === argHash.id){
            if (!showHash.noJump) {
              this.viewer.helpers.jump(argHash.index, offset);
            }
            this.pages['p'+i].annotations[n].show(showHash);
            return;
          }
        }
      }
    }

    if (!showHash.noJump){
      this.viewer.helpers.jump(argHash.index,offset);
    }

  }
};
<commit_msg>PageSet's pages property needs to be a hash<commit_after>DV.PageSet = function(viewer){
  this.currentPage  = null;
  this.pages        = {};
  this.viewer       = viewer;
  this.zoomText();
};

// used to call the same method with the same params against all page instances
DV.PageSet.prototype.execute = function(action,params){
  this.pages.each(function(pageInstance){
    pageInstance[action].apply(pageInstance,params);
  });
};

// build the basic page presentation layer
DV.PageSet.prototype.buildPages = function(options) {
  options = options || {};
  var pages = this.getPages();
  for(var i = 0; i < pages.length; i++) {
    var page  = pages[i];
    page.set  = this;
    page.index = i;

    // TODO: Make more explicit, this is sloppy
    this.pages[page.label] = new DV.Page(this.viewer, page);

    if(page.currentPage == true) {
      this.currentPage = this.pages[page.label];
    }
  }
  this.viewer.models.annotations.renderAnnotations();
};

// used to generate references for the build action
DV.PageSet.prototype.getPages = function(){
  var _pages = [];

  this.viewer.elements.sets.each(function(_index,el){

    var currentPage = (_index == 0) ? true : false;
    _pages.push({ label: 'p'+_index, el: el, index: _index, pageNumber: _index+1, currentPage: currentPage });

  });

  return _pages;
};

// basic reflow to ensure zoomlevel is right, pages are in the right place and annotation limits are correct
DV.PageSet.prototype.reflowPages = function() {
  this.viewer.models.pages.resize();
  this.viewer.helpers.setActiveAnnotationLimits();
  this.redraw(false, true);
};

// reflow the pages without causing the container to resize or annotations to redraw
DV.PageSet.prototype.simpleReflowPages = function(){
  this.viewer.helpers.setActiveAnnotationLimits();
  this.redraw(false, false);
};

// hide any active annotations
DV.PageSet.prototype.cleanUp = function(){
  if(this.viewer.activeAnnotation){
    this.viewer.activeAnnotation.hide(true);
  }
};

DV.PageSet.prototype.zoom = function(argHash){
  if(this.viewer.models.document.zoomLevel === argHash.zoomLevel) return;

  var currentPage   = this.viewer.models.document.currentIndex();
  var _index      = currentPage - 1;
  var oldOffset     = this.viewer.models.document.offsets[currentPage];

  var oldZoom      = this.viewer.models.document.zoomLevel*1;
  var scrollPos    = this.viewer.elements.window.scrollTop();

  this.viewer.models.document.zoom(argHash.zoomLevel);

  var diff      = (parseInt(scrollPos, 10)>parseInt(oldOffset, 10)) ? scrollPos - oldOffset : oldOffset - scrollPos;

  var diffPercentage   = diff / this.viewer.models.pages.height;

  // this.position();
  this.reflowPages();
  this.zoomText();

  if(this.viewer.activeAnnotation != null){
    // FIXME:

    var args =
    {
      index: this.viewer.models.document.currentIndex(),
      top: this.viewer.activeAnnotation.y1,
      id: this.viewer.activeAnnotation.id
    };
    this.viewer.activeAnnotation = null;

    this.showAnnotation(args);
    this.viewer.helpers.setActiveAnnotationLimits(this.viewer.activeAnnotation);
  }else{
    var _offset      = Math.round(this.viewer.models.pages.height * diffPercentage);
    this.viewer.helpers.jump(this.viewer.models.document.currentIndex(),_offset);
  }
};

// Zoom the text container.
DV.PageSet.prototype.zoomText = function() {
  var padding = this.viewer.models.pages.DEFAULT_PADDING;
  var width   = this.viewer.models.pages.zoomLevel;
  this.viewer.$('.DV-textContents').width(width - padding);
  this.viewer.$('.DV-textPage').width(width);
  if (this.viewer.options.zoom == 'auto') {
    padding = this.viewer.models.pages.REDUCED_PADDING;
  }
  this.viewer.elements.collection.css({'width' : width + padding});
};

// draw the pages
DV.PageSet.prototype.draw = function(pageCollection){
  for(var i = 0, pageCollectionLength = pageCollection.length; i < pageCollectionLength;i++){
    var page = this.pages[pageCollection[i].label];
    if (page) page.draw({ index: pageCollection[i].index, pageNumber: pageCollection[i].index+1});
  }
};

DV.PageSet.prototype.redraw = function(stopResetOfPosition, redrawAnnotations) {
  var _index = this.viewer.models.document.currentIndex();
  if (this.pages['p0']) this.pages['p0'].draw({ force: true, forceAnnotationRedraw : redrawAnnotations });
  if (this.pages['p1']) this.pages['p1'].draw({ force: true, forceAnnotationRedraw : redrawAnnotations });
  if (this.pages['p2']) this.pages['p2'].draw({ force: true, forceAnnotationRedraw : redrawAnnotations });

  if(redrawAnnotations && this.viewer.activeAnnotation){
    this.viewer.helpers.jump(this.viewer.activeAnnotation.page.index,this.viewer.activeAnnotation.position.top - 37);
  }
};

// set the annotation to load ahead of time
DV.PageSet.prototype.setActiveAnnotation = function(annotationId, edit){
  this.viewer.annotationToLoadId   = annotationId;
  this.viewer.annotationToLoadEdit = edit ? annotationId : null;
};

// a funky fucking mess to jump to the annotation that is active
DV.PageSet.prototype.showAnnotation = function(argHash, showHash){
  showHash = showHash || {};

  // if state is ViewAnnotation, jump to the appropriate position in the view
  // else
  // hide active annotations and locate the position of the next annotation
  // NOTE: This needs work
  if(this.viewer.state === 'ViewAnnotation'){

    var offset = this.viewer.$('.DV-allAnnotations div[rel=aid-'+argHash.id+']')[0].offsetTop;
    this.viewer.elements.window.scrollTop(offset+10,'fast');
    this.viewer.helpers.setActiveAnnotationInNav(argHash.id);
    this.viewer.activeAnnotationId = argHash.id;
    this.viewer.history.save('annotation/a'+argHash.id);
    return;
  }else{
    this.viewer.helpers.removeObserver('trackAnnotation');
    this.viewer.activeAnnotationId = null;
    if(this.viewer.activeAnnotation != null){
      this.viewer.activeAnnotation.hide();
    }
    this.setActiveAnnotation(argHash.id, showHash.edit);

    var isPage = this.viewer.models.annotations.byId[argHash.id].type == 'page';
    var nudge  = isPage ? -7 : 36;
    var offset = (argHash.top * this.viewer.models.pages.zoomFactor()) - nudge;

    for(var i = 0; i <= 2; i++){
      if (this.pages['p' + i]) {
        for(var n = 0; n < this.pages['p'+i].annotations.length; n++){
          if(this.pages['p'+i].annotations[n].id === argHash.id){
            if (!showHash.noJump) {
              this.viewer.helpers.jump(argHash.index, offset);
            }
            this.pages['p'+i].annotations[n].show(showHash);
            return;
          }
        }
      }
    }

    if (!showHash.noJump){
      this.viewer.helpers.jump(argHash.index,offset);
    }

  }
};
<|endoftext|>


<commit_before>var util = require('util')
  , auth = require('../auth')
  , moment = require('moment')
  , form = require('express-form')
  , field = form.field
  , utilities = require('../utilities')
  , mongoose = require('mongoose')
  , Experimonth = mongoose.model('Experimonth')
  , ExperimonthKind = mongoose.model('ExperimonthKind')
  , ExperimonthEnrollment = mongoose.model('ExperimonthEnrollment')
/*   , Game = mongoose.model('Game') */
  , User = mongoose.model('User')
  , Event = mongoose.model('Event')
  , ProfileQuestion = mongoose.model('ProfileQuestion')
  , ProfileAnswer = mongoose.model('ProfileAnswer')
  , async = require('async');

module.exports = function(app){

	app.get('/profile/questions', auth.authorize(2, 10), function(req, res){
		// Your profile questions
		ProfileQuestion.find({}).exec(function(err, questions){
			res.render('profile/questions', {title: 'Profile Questions', questions: questions});
		});
	});

	// (as, populate, title, object, template, varNames, redirect, beforeRender, beforeSave)
	var as = 'question'
	  , populate = []
	  , template = 'profile/questions/form'
	  , varNames = ['text', 'type', 'required', 'slug']
	  , redirect = '/profile/questions'
	  , formValidate = form(
			field('text').trim().required()
		  , field('type').trim().required()
		  , field('required').trim()
		  , field('choices_string').trim()
		  , field('slug').trim()
		)
	  , beforeRender = function(req, res, item, callback){
/*
			if(item.published){
				req.flash('error', 'This Profile Question has already been published and cannot be edited.');
				res.redirect('back');
				return;
			}
*/
			return callback(item);
		}
	  , beforeSave = function(req, res, item, complete){
			// Convert choices_string into an array of strings
			if(req.param('choices_string')){
				var choices = req.param('choices_string');
				choices = choices.split(',');
				for(var i=0; i<choices.length; i++){
					choices[i] = choices[i].trim();
				}
				item.choices = choices;
			}
			complete(item);
			return;
	/*
			// Email to Beck
			// setup e-mail data with unicode symbols
			Confession.count(function(err, count){
				item.number = count+1;

				var mailOptions = {
			    	from: "Experimonth: Frenemy <experimonth@lifeandscience.org>", // sender address
			    	to: 'experimonth+confessional@lifeandscience.org', // list of receivers
			    	subject: 'New confession.', // Subject line
			    	text: 'New Confessional posted on '+moment(item.date).format('YYYY-MM-DD hh:mm A')+'\n\n---\n\n'+item.text
			    };

			    // send mail with defined transport object
				email.sendMail(mailOptions, null);
				complete(item);
			});
	*/
		}
	  , layout = 'layout';

	app.get('/profile/questions/add', auth.authorize(2, 10), utilities.doForm(as, populate, 'Add Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, null, layout));
	app.post('/profile/questions/add', auth.authorize(2, 10), formValidate, utilities.doForm(as, populate, 'Add Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, beforeSave, layout));

	app.get('/profile/questions/edit/:id', auth.authorize(2, 10), utilities.doForm(as, populate, 'Edit Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, null, layout));
	app.post('/profile/questions/edit/:id', auth.authorize(2, 10), formValidate, utilities.doForm(as, populate, 'Add Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, beforeSave, layout));

	app.get('/profile/questions/delete/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		ProfileQuestion.findById(req.param('id')).exec(function(err, question){
			if(err || !question){
				req.flash('error', 'Profile Question not found.');
				res.redirect('back');
				return;
			}
			if(question.published){
				req.flash('error', 'A published question may not be deleted.');
				res.redirect('back');
				return;
			}
			question.remove(function(err){
				if(err){
					req.flash('error', 'Error while deleting profile question: '+err);
					res.redirect('back');
					return;
				}
				ProfileAnswer.remove({question: question._id}).exec(function(err){
					console.log('removed all related answers also: ', err);
				});
				req.flash('info', 'Profile Question deleted successfully.');
				res.redirect('back');
				return;
			});
		});
	});

	app.get('/profile/questions/publish/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		ProfileQuestion.findById(req.param('id')).exec(function(err, question){
			if(err || !question){
				req.flash('error', 'Profile Question not found.');
				res.redirect('back');
				return;
			}
/*
			if(question.published){
				req.flash('error', 'A published question may not be re-published.');
				res.redirect('back');
				return;
			}
*/
			question.published = !question.published;
			question.publishDate = new Date();
			question.save(function(err){
				if(err){
					req.flash('error', 'Error while '+(question.published ? 'publishing' : 'unpublishing')+' profile question: '+err);
					res.redirect('back');
					return;
				}
				// Send a notification to all existing users that a new question was published.
/*
				User.notifyAll('info', null, 'New Profile Question Available.', 'Please check out the new profile question that was just published.', function(err){
					if(err){
						req.flash('error', 'Error notifying users. '+err);
						res.redirect('back');
						return;
					}
*/
/* 					if(question.required){ */
/* 						return User.reCheckAllUsersProfileQuestions(function(){ */
							req.flash('info', 'Profile Question '+(question.published ? 'published' : 'unpublished')+' successfully.');
							res.redirect('back');
							return;
/* 						}); */
/*
					}
					req.flash('info', 'Profile Question '+(question.published ? 'published' : 'unpublished')+' successfully.');
					res.redirect('back');
*/
					return;
/* 				}); */
			});
		});
	});

	app.post('/profile/questions/answer/:id', auth.authorize(2, 0, null, true), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		console.log('full: ', req.body);
		if(!req.param('value') && !req.param('no_answer') && req.param('submit') != 'Choose not to answer'){
			req.flash('error', 'Please answer the question or click \'Choose not to answer\'.');
			res.redirect('back');
			return;
		}
		ProfileQuestion.findById(req.param('id')).exec(function(err, question){
			if(err || !question){
				req.flash('error', 'Profile Question not found.');
				res.redirect('back');
				return;
			}

			if(req.param('answerid')){
				ProfileAnswer.findById(req.param('answerid')).exec(function(err, answer){
					if(err || !answer){
						req.flash('error', 'Previous answer couldn\'t be retrieved');
						res.redirect('back');
						return;
					}
					answer.value = req.param('value');
					answer.no_answer = req.body.submit == 'Choose not to answer';
					if(answer.no_answer){
						answer.value = null;
					}
					answer.save(function(err){
						if(err){
							req.flash('error', 'Error saving existing answer.');
							res.redirect('back');
							return;
						}
						req.flash('info', 'Thanks for your answer.');
						res.redirect('back');
						return;
					});
				});
				return;
			}

			var answer = new ProfileAnswer();
			answer.user = req.user._id;
			answer.question = req.param('id');
			answer.value = req.param('value');
			answer.no_answer = req.body.submit == 'Choose not to answer';
			if(answer.no_answer){
				answer.value = null;
			}
			answer.save(function(err){
				if(err){
					req.flash('error', 'Error saving new answer.');
					res.redirect('back');
					return;
				}

				req.user.answers.push(answer._id);
				req.user.save(function(err){
					if(err){
						req.flash('error', 'Error saving user.');
						res.redirect('back');
						return;
					}

					req.user.reCheckProfileQuestions(null, function(){
						req.flash('info', 'Thanks for your answer.');
						res.redirect('back');
					});
				});
				return;
			});
		});
	});

	var doAdditionalInfo = function(user, res){
		ProfileAnswer.find({user: user._id}).populate('question').exec(function(err, answers){
			var questionsToIgnore = [];
/*
			for(var i=0; i<answers.length; i++){
				if(answers[i].question){
					questionsToIgnore.push(answers[i].question._id.toString());
				}
			}
*/
			user.getProfileQuestions(questionsToIgnore, function(err, questions, questionIds, optionalQuestions, optionalQuestionIds){
				for(var i=0; i<answers.length; i++){
					if(answers[i].question){
						var idx = questionIds.indexOf(answers[i].question._id.toString());
						if(idx != -1){
							answers[i].question.transientRequired = true;
							questionIds.splice(idx, 1);
							questions.splice(idx, 1);
						}
						idx = optionalQuestionIds.indexOf(answers[i].question._id.toString());
						if(idx != -1){
							optionalQuestionIds.splice(idx, 1);
							optionalQuestions.splice(idx, 1);
						}
					}
				}
				res.render('profile/additional_info', {title: 'Your Additional Info', u: user, questions: questions, optionalQuestions: optionalQuestions, answers: answers});
			});
		});
	}
	app.get('/profile/additional-info', auth.authorize(1, 0, null, true), function(req, res){
		doAdditionalInfo(req.user, res);
	});
	app.get('/profile/additional-info/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		User.findById(req.param('id'))/* .populate('experimonths') */.exec(function(err, user){
			if(err || !user){
				req.flash('error', 'User not found.');
				res.redirect('back');
				return;
			}
			doAdditionalInfo(user, res);
		});
	});

	var doProfile = function(user, req, res){
		async.parallel({
			enrollments: function(callback){
				ExperimonthEnrollment.find({_id: {$in: user.enrollments}}).populate('experimonth').exec(callback);
			}
		  , events: function(callback){
				Event.find({user: user._id}).populate('kind').populate('experimonth').exec(callback);
			}
		}, function(err, results){
			if(err){
				req.flash('error', err);
				res.redirect('back');
				return;
			}
			if(!results.enrollments){
				req.flash('error', 'Enrollments not found.');
				res.redirect('back');
				return;
			}
			if(!results.events){
				req.flash('error', 'Events not found.');
				res.redirect('back');
				return;
			}
			async.map(results.enrollments, function(enrollment, callback){
				ExperimonthKind.findById(enrollment.experimonth.kind).exec(function(err, kind){
					if(err){
						callback(err);
						return;
					}
					enrollment.experimonth.kindPopulated = kind;
					return callback(null, enrollment);
				});
			}, function(err, enrollments){
				ProfileAnswer.find({user: user._id}).populate('question').exec(function(err, answers){
					var questionsToIgnore = [];
					for(var i=0; i<answers.length; i++){
						if(answers[i].question){
							questionsToIgnore.push(answers[i].question._id.toString());
						}
					}
					user.getProfileQuestions(questionsToIgnore, function(err, questions, questionIds, optionalQuestions, optionalQuestionIds){
						res.render('profile', {title: 'Your Experimonth Profile', u: user, enrollments: enrollments, questions: questions, optionalQuestions: optionalQuestions, answers: answers, timezones: utilities.getTimezones()/* , games: games */, events: results.events, linkAppend: (user == req.user ? '' : '/'+user._id)});
					});
				});
			});
		});
	}
	app.get('/profile', auth.authorize(1, 0, null, true), function(req, res){
//		console.log('user.timezone', utilities.getTimezoneFromOffset(req.user.timezone));
		req.flash('question');
		if(!req.user.hasAnsweredAllRequiredQuestions){
			req.flash('error', 'You must answer any required profile questions before participating in an Experimonth.');
		}
		doProfile(req.user, req, res);
	});

	app.get('/profile/mark-all-read', auth.authorize(1, 0, null, true), function(req, res){
		if(res.locals.notifications && res.locals.notifications.length){
			async.each(res.locals.notifications, function(notification, callback){
				notification.read = true;
				notification.save(callback);
			}, function(err){
				res.redirect('back');
				return;
			})
			return;
		}
		res.redirect('back');
	});

	app.get('/profile/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		User.findById(req.param('id'))/* .populate('experimonths') */.exec(function(err, user){
			if(err || !user){
				req.flash('error', 'User not found.');
				res.redirect('back');
				return;
			}
			doProfile(user, req, res);
		});
	});
};<commit_msg>Profile Completion Percentage.<commit_after>var util = require('util')
  , auth = require('../auth')
  , moment = require('moment')
  , form = require('express-form')
  , field = form.field
  , utilities = require('../utilities')
  , mongoose = require('mongoose')
  , Experimonth = mongoose.model('Experimonth')
  , ExperimonthKind = mongoose.model('ExperimonthKind')
  , ExperimonthEnrollment = mongoose.model('ExperimonthEnrollment')
/*   , Game = mongoose.model('Game') */
  , User = mongoose.model('User')
  , Event = mongoose.model('Event')
  , ProfileQuestion = mongoose.model('ProfileQuestion')
  , ProfileAnswer = mongoose.model('ProfileAnswer')
  , async = require('async');

module.exports = function(app){

	app.get('/profile/questions', auth.authorize(2, 10), function(req, res){
		// Your profile questions
		ProfileQuestion.find({}).exec(function(err, questions){
			res.render('profile/questions', {title: 'Profile Questions', questions: questions});
		});
	});

	// (as, populate, title, object, template, varNames, redirect, beforeRender, beforeSave)
	var as = 'question'
	  , populate = []
	  , template = 'profile/questions/form'
	  , varNames = ['text', 'type', 'required', 'slug']
	  , redirect = '/profile/questions'
	  , formValidate = form(
			field('text').trim().required()
		  , field('type').trim().required()
		  , field('required').trim()
		  , field('choices_string').trim()
		  , field('slug').trim()
		)
	  , beforeRender = function(req, res, item, callback){
/*
			if(item.published){
				req.flash('error', 'This Profile Question has already been published and cannot be edited.');
				res.redirect('back');
				return;
			}
*/
			return callback(item);
		}
	  , beforeSave = function(req, res, item, complete){
			// Convert choices_string into an array of strings
			if(req.param('choices_string')){
				var choices = req.param('choices_string');
				choices = choices.split(',');
				for(var i=0; i<choices.length; i++){
					choices[i] = choices[i].trim();
				}
				item.choices = choices;
			}
			complete(item);
			return;
	/*
			// Email to Beck
			// setup e-mail data with unicode symbols
			Confession.count(function(err, count){
				item.number = count+1;

				var mailOptions = {
			    	from: "Experimonth: Frenemy <experimonth@lifeandscience.org>", // sender address
			    	to: 'experimonth+confessional@lifeandscience.org', // list of receivers
			    	subject: 'New confession.', // Subject line
			    	text: 'New Confessional posted on '+moment(item.date).format('YYYY-MM-DD hh:mm A')+'\n\n---\n\n'+item.text
			    };

			    // send mail with defined transport object
				email.sendMail(mailOptions, null);
				complete(item);
			});
	*/
		}
	  , layout = 'layout';

	app.get('/profile/questions/add', auth.authorize(2, 10), utilities.doForm(as, populate, 'Add Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, null, layout));
	app.post('/profile/questions/add', auth.authorize(2, 10), formValidate, utilities.doForm(as, populate, 'Add Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, beforeSave, layout));

	app.get('/profile/questions/edit/:id', auth.authorize(2, 10), utilities.doForm(as, populate, 'Edit Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, null, layout));
	app.post('/profile/questions/edit/:id', auth.authorize(2, 10), formValidate, utilities.doForm(as, populate, 'Add Profile Question', ProfileQuestion, template, varNames, redirect, beforeRender, beforeSave, layout));

	app.get('/profile/questions/delete/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		ProfileQuestion.findById(req.param('id')).exec(function(err, question){
			if(err || !question){
				req.flash('error', 'Profile Question not found.');
				res.redirect('back');
				return;
			}
			if(question.published){
				req.flash('error', 'A published question may not be deleted.');
				res.redirect('back');
				return;
			}
			question.remove(function(err){
				if(err){
					req.flash('error', 'Error while deleting profile question: '+err);
					res.redirect('back');
					return;
				}
				ProfileAnswer.remove({question: question._id}).exec(function(err){
					console.log('removed all related answers also: ', err);
				});
				req.flash('info', 'Profile Question deleted successfully.');
				res.redirect('back');
				return;
			});
		});
	});

	app.get('/profile/questions/publish/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		ProfileQuestion.findById(req.param('id')).exec(function(err, question){
			if(err || !question){
				req.flash('error', 'Profile Question not found.');
				res.redirect('back');
				return;
			}
/*
			if(question.published){
				req.flash('error', 'A published question may not be re-published.');
				res.redirect('back');
				return;
			}
*/
			question.published = !question.published;
			question.publishDate = new Date();
			question.save(function(err){
				if(err){
					req.flash('error', 'Error while '+(question.published ? 'publishing' : 'unpublishing')+' profile question: '+err);
					res.redirect('back');
					return;
				}
				// Send a notification to all existing users that a new question was published.
/*
				User.notifyAll('info', null, 'New Profile Question Available.', 'Please check out the new profile question that was just published.', function(err){
					if(err){
						req.flash('error', 'Error notifying users. '+err);
						res.redirect('back');
						return;
					}
*/
/* 					if(question.required){ */
/* 						return User.reCheckAllUsersProfileQuestions(function(){ */
							req.flash('info', 'Profile Question '+(question.published ? 'published' : 'unpublished')+' successfully.');
							res.redirect('back');
							return;
/* 						}); */
/*
					}
					req.flash('info', 'Profile Question '+(question.published ? 'published' : 'unpublished')+' successfully.');
					res.redirect('back');
*/
					return;
/* 				}); */
			});
		});
	});

	app.post('/profile/questions/answer/:id', auth.authorize(2, 0, null, true), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		console.log('full: ', req.body);
		if(!req.param('value') && !req.param('no_answer') && req.param('submit') != 'Choose not to answer'){
			req.flash('error', 'Please answer the question or click \'Choose not to answer\'.');
			res.redirect('back');
			return;
		}
		ProfileQuestion.findById(req.param('id')).exec(function(err, question){
			if(err || !question){
				req.flash('error', 'Profile Question not found.');
				res.redirect('back');
				return;
			}

			if(req.param('answerid')){
				ProfileAnswer.findById(req.param('answerid')).exec(function(err, answer){
					if(err || !answer){
						req.flash('error', 'Previous answer couldn\'t be retrieved');
						res.redirect('back');
						return;
					}
					answer.value = req.param('value');
					answer.no_answer = req.body.submit == 'Choose not to answer';
					if(answer.no_answer){
						answer.value = null;
					}
					answer.save(function(err){
						if(err){
							req.flash('error', 'Error saving existing answer.');
							res.redirect('back');
							return;
						}
						req.flash('info', 'Thanks for your answer.');
						res.redirect('back');
						return;
					});
				});
				return;
			}

			var answer = new ProfileAnswer();
			answer.user = req.user._id;
			answer.question = req.param('id');
			answer.value = req.param('value');
			answer.no_answer = req.body.submit == 'Choose not to answer';
			if(answer.no_answer){
				answer.value = null;
			}
			answer.save(function(err){
				if(err){
					req.flash('error', 'Error saving new answer.');
					res.redirect('back');
					return;
				}

				req.user.answers.push(answer._id);
				req.user.save(function(err){
					if(err){
						req.flash('error', 'Error saving user.');
						res.redirect('back');
						return;
					}

					req.user.reCheckProfileQuestions(null, function(){
						req.flash('info', 'Thanks for your answer.');
						res.redirect('back');
					});
				});
				return;
			});
		});
	});

	var doAdditionalInfo = function(user, res){
		ProfileAnswer.find({user: user._id}).populate('question').exec(function(err, answers){
			var questionsToIgnore = [];
/*
			for(var i=0; i<answers.length; i++){
				if(answers[i].question){
					questionsToIgnore.push(answers[i].question._id.toString());
				}
			}
*/
			user.getProfileQuestions(questionsToIgnore, function(err, questions, questionIds, optionalQuestions, optionalQuestionIds){
				for(var i=0; i<answers.length; i++){
					if(answers[i].question){
						var idx = questionIds.indexOf(answers[i].question._id.toString());
						if(idx != -1){
							answers[i].question.transientRequired = true;
							questionIds.splice(idx, 1);
							questions.splice(idx, 1);
						}
						idx = optionalQuestionIds.indexOf(answers[i].question._id.toString());
						if(idx != -1){
							optionalQuestionIds.splice(idx, 1);
							optionalQuestions.splice(idx, 1);
						}
					}
				}
				res.render('profile/additional_info', {title: 'Your Additional Info', u: user, questions: questions, optionalQuestions: optionalQuestions, answers: answers});
			});
		});
	}
	app.get('/profile/additional-info', auth.authorize(1, 0, null, true), function(req, res){
		doAdditionalInfo(req.user, res);
	});
	app.get('/profile/additional-info/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		User.findById(req.param('id'))/* .populate('experimonths') */.exec(function(err, user){
			if(err || !user){
				req.flash('error', 'User not found.');
				res.redirect('back');
				return;
			}
			doAdditionalInfo(user, res);
		});
	});

	var doProfile = function(user, req, res){
		async.parallel({
			enrollments: function(callback){
				ExperimonthEnrollment.find({_id: {$in: user.enrollments}}).populate('experimonth').exec(callback);
			}
		  , events: function(callback){
				Event.find({user: user._id}).populate('kind').populate('experimonth').exec(callback);
			}
		}, function(err, results){
			if(err){
				req.flash('error', err);
				res.redirect('back');
				return;
			}
			if(!results.enrollments){
				req.flash('error', 'Enrollments not found.');
				res.redirect('back');
				return;
			}
			if(!results.events){
				req.flash('error', 'Events not found.');
				res.redirect('back');
				return;
			}
			async.map(results.enrollments, function(enrollment, callback){
				ExperimonthKind.findById(enrollment.experimonth.kind).exec(function(err, kind){
					if(err){
						callback(err);
						return;
					}
					enrollment.experimonth.kindPopulated = kind;
					return callback(null, enrollment);
				});
			}, function(err, enrollments){
				ProfileAnswer.find({user: user._id}).populate('question').exec(function(err, answers){
					var questionsToIgnore = [];
					for(var i=0; i<answers.length; i++){
						if(answers[i].question){
							questionsToIgnore.push(answers[i].question._id.toString());
						}
					}
					user.getProfileQuestions(questionsToIgnore, function(err, questions, questionIds, optionalQuestions, optionalQuestionIds){
						var percentage = questionsToIgnore.length / (questions.length+optionalQuestions.length+questionsToIgnore.length);
						percentage = Math.round(percentage * 100);
						res.render('profile', {title: 'Your Experimonth Profile is '+percentage+'% Complete', u: user, enrollments: enrollments, questions: questions, optionalQuestions: optionalQuestions, answers: answers, timezones: utilities.getTimezones()/* , games: games */, events: results.events, linkAppend: (user == req.user ? '' : '/'+user._id)});
					});
				});
			});
		});
	}
	app.get('/profile', auth.authorize(1, 0, null, true), function(req, res){
//		console.log('user.timezone', utilities.getTimezoneFromOffset(req.user.timezone));
		req.flash('question');
		if(!req.user.hasAnsweredAllRequiredQuestions){
			req.flash('error', 'You must answer any required profile questions before participating in an Experimonth.');
		}
		doProfile(req.user, req, res);
	});

	app.get('/profile/mark-all-read', auth.authorize(1, 0, null, true), function(req, res){
		if(res.locals.notifications && res.locals.notifications.length){
			async.each(res.locals.notifications, function(notification, callback){
				notification.read = true;
				notification.save(callback);
			}, function(err){
				res.redirect('back');
				return;
			})
			return;
		}
		res.redirect('back');
	});

	app.get('/profile/:id', auth.authorize(2, 10), function(req, res){
		if(!req.param('id')){
			req.flash('error', 'Missing Profile Question ID.');
			res.redirect('back');
			return;
		}
		User.findById(req.param('id'))/* .populate('experimonths') */.exec(function(err, user){
			if(err || !user){
				req.flash('error', 'User not found.');
				res.redirect('back');
				return;
			}
			doProfile(user, req, res);
		});
	});
};<|endoftext|>


<commit_before>'use strict';

module.exports = {
    "mergeACLArray": function (arrayACL, cb) {

        return cb(null, null);
    }
};<commit_msg>added mergeACLArray code to lib<commit_after>'use strict';
let async = require("async");
let _ = require("lodash");

module.exports = {
    "mergeACLArray": function (arrayACL, cb) {

	    //if only one return it
	    if (arrayACL.length === 0){
		    return cb(null, arrayACL[0]);
	    }
	    let acl = {};
	    async.parallel({
			    acl: function (callback) {
				    async.each(arrayACL, mergePackageAcl, callback);
			    },
			    acl_all_env: function (callback) {
				    async.each(arrayACL, mergePackageAclAllEnv, callback);
			    }
		    },
		    function () {
			    return cb(null, acl);
		    });

	    function mergePackageAcl(oneAcl, cb) {
		    //first object
		    if (!acl.acl) {
			    acl.acl = oneAcl.acl;
			    return cb();
		    }
		    async.eachOf(oneAcl.acl, function (service, serviceName, serviceCall) {
			    if (!acl.acl[serviceName]) {
				    acl.acl[serviceName] = service;
				    return serviceCall();
			    }
			    async.eachOf(service, function (version, versionValue, versionCall) {
				    if (!acl.acl[serviceName][versionValue]) {
					    acl.acl[serviceName][versionValue] = version;
					    return serviceCall();
				    }
				    if (version.apisPermission === "restricted") {
					    if (acl.acl[serviceName] && acl.acl[serviceName][versionValue] && acl.acl[serviceName][versionValue].apisPermission && acl.acl[serviceName][versionValue].apisPermission === "restricted") {
						    //only restricted allowed to merge
						    acl.acl[serviceName][versionValue] = _.merge(acl.acl[serviceName][versionValue], version);
						    return versionCall();
					    } else {
						    acl.acl[serviceName][versionValue] = version;
						    return versionCall();
					    }
				    } else {
					    //keep the old
					    if (acl.acl[serviceName] && acl.acl[serviceName][versionValue] && acl.acl[serviceName][versionValue].apisPermission && acl.acl[serviceName][versionValue].apisPermission === "restricted") {
						    return versionCall();
					    } else {
						    //only non restricted allowed to merge
						    acl.acl[serviceName][versionValue] = _.merge(acl.acl[serviceName][versionValue], version);
						    return versionCall();
					    }
				    }
			    }, serviceCall);
		    }, cb);
	    }

	    function mergePackageAclAllEnv(oneAcl, cb) {
		    //first object
		    if (!acl.acl_all_env) {
			    acl.acl_all_env = oneAcl.acl_all_env;
			    return cb();
		    }
		    async.eachOf(oneAcl.acl_all_env, function (environment, envName, envCall) {
			    if (!acl.acl_all_env[envName]) {
				    acl.acl_all_env[envName] = environment;
				    return envCall();
			    }
			    async.eachOf(environment, function (service, serviceName, serviceCall) {
				    if (!acl.acl_all_env[envName][serviceName]) {
					    acl.acl_all_env[envName][serviceName] = service;
					    return serviceCall();
				    }
				    async.eachOf(service, function (version, versionValue, versionCall) {
					    if (!acl.acl_all_env[envName][serviceName][versionValue]) {
						    acl.acl_all_env[envName][serviceName][versionValue] = version;
						    return serviceCall();
					    }
					    if (version.apisPermission === "restricted") {
						    if (acl.acl_all_env[envName][serviceName] && acl.acl_all_env[envName][serviceName][versionValue] && acl.acl_all_env[envName][serviceName][versionValue].apisPermission && acl.acl_all_env[envName][serviceName][versionValue].apisPermission === "restricted") {
							    //only restricted allowed to merge
							    acl.acl_all_env[envName][serviceName][versionValue] = _.merge(acl.acl_all_env[envName][serviceName][versionValue], version);
							    return versionCall();
						    } else {
							    acl.acl_all_env[envName][serviceName][versionValue] = version;
							    return versionCall();
						    }
					    } else {
						    //keep the old
						    if (acl.acl_all_env[envName][serviceName] && acl.acl_all_env[envName][serviceName][versionValue] && acl.acl_all_env[envName][serviceName][versionValue].apisPermission && acl.acl_all_env[envName][serviceName][versionValue].apisPermission === "restricted") {
							    return versionCall();
						    } else {
							    //only non restricted allowed to merge
							    acl.acl_all_env[envName][serviceName][versionValue] = _.merge(acl.acl_all_env[envName][serviceName][versionValue], version);
							    return versionCall();
						    }
					    }
				    }, serviceCall);
			    }, envCall);
		    }, cb);
	    }
    }
};<|endoftext|>


<commit_before>import React, { Component } from 'react'
import Radium from 'radium'
import {
  Text,
} from '../../../components'
import colors from '../../../styles/colors'
import { mediaQueries } from '../../../styles/common'

const iconSmallWidth = 85
const iconMediumWidth = 115
const iconLargeWidth = 125
const iconMaxHeightRatio = 0.85

const styles = {
  base: {
    display: 'inline-block'
  },
  icon: {
    width: `${iconSmallWidth}px`,
    maxHeight: `${iconSmallWidth*iconMaxHeightRatio}px`,
    marginBottom: '5px',
    [mediaQueries.mediumUp]: {
      width: `${iconMediumWidth}px`,
      maxHeight: `${iconMediumWidth*iconMaxHeightRatio}px`,
      marginBottom: '20px'
    },
    [mediaQueries.largeUp]: {
      width: `${iconLargeWidth}px`,
      maxHeight: `${iconLargeWidth*iconMaxHeightRatio}px`,
    }
  },
  text: {
    fontWeight: 'bold',
    color: colors.offBlack,
    fontSize: '18px',
    [mediaQueries.mediumUp]: {
      fontSize: '28px',
    }
  }
}

class Step extends Component {
  render() {
    const { icon, iconAlt, text } = this.props

    return (

        <Text style={styles.text}>{text}</Text>

    )
  }
}

export default Radium(Step)
<commit_msg>StartPage steps have equal width<commit_after>import React, { Component } from 'react'
import Radium from 'radium'
import {
  Text,
} from '../../../components'
import colors from '../../../styles/colors'
import { mediaQueries } from '../../../styles/common'

const iconSmallWidth = 85
const iconMediumWidth = 115
const iconLargeWidth = 125
const iconMaxHeightRatio = 0.85

const styles = {
  base: {
    display: 'inline-block',
    width: '150px',
  },
  icon: {
    width: `${iconSmallWidth}px`,
    maxHeight: `${iconSmallWidth*iconMaxHeightRatio}px`,
    marginBottom: '5px',
    [mediaQueries.mediumUp]: {
      width: `${iconMediumWidth}px`,
      maxHeight: `${iconMediumWidth*iconMaxHeightRatio}px`,
      marginBottom: '20px'
    },
    [mediaQueries.largeUp]: {
      width: `${iconLargeWidth}px`,
      maxHeight: `${iconLargeWidth*iconMaxHeightRatio}px`,
    }
  },
  text: {
    fontWeight: 'bold',
    color: colors.offBlack,
    fontSize: '18px',
    [mediaQueries.mediumUp]: {
      fontSize: '28px',
    }
  }
}

class Step extends Component {
  render() {
    const { icon, iconAlt, text } = this.props

    return (

        <Text style={styles.text}>{text}</Text>

    )
  }
}

export default Radium(Step)
<|endoftext|>


<commit_before>var techtxtdetector = (function() {
    var appcontent = null,
        initialized = false,
        icon = null,
        prefs = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefBranch).getBranch('techtxtdetector.');

    var publicFunctions = {
        'handleBtn': handleBtn
    };

    /**
     * Wrapper method for all findUrl* methods. Once a suitable method
     * returns an URL or no tech.txt is found at all, callback is executed
     * with the URL or "undefined" as argument.
     *
     */
    function findUrl(window, doc, tab, callback) {
        var url = getTechUrl(doc);
        if (url === null) {
            if (prefs.getBoolPref('methods.headlink') === true) {
                url = findUrlInDom(window.content.document);
            }
            if ((url === null || url === false) && prefs.getBoolPref('methods.domainfile') === true) {
                findUrlOnDomain(doc, function(foundUrl) {
                    setTechUrl(doc, foundUrl);
                    callback(foundUrl, window, doc, tab);
                });
                return;
            }
            setTechUrl(doc, url);
        }
        callback(url, window, doc, tab);
    }

    /**
     * Checks the DOM for a reference to a tech.txt file and returns it if
     * found. Otherwise false is returned.
     */
    function findUrlInDom(doc) {
        var links = doc.querySelectorAll('head>link'),
            linkCnt = links.length,
            link = null;
        for (var i = 0; i < linkCnt; i++) {
            link = links[0];
            if (link.getAttribute('rel') === 'tech.txt') {
                return link.getAttribute('href');
            }
        }
        return false;
    }

    function findUrlOnDomain(doc, callback) {
        var loc = window.content.location,
            url = loc.protocol + '//' + loc.host + '/tech.txt',
            req = new XMLHttpRequest(),
            lockKey = 'techtxt-lookup-in-progress';
        // Don't do a lookup on file://
        if (loc.protocol === 'file:') return callback(false);
        // Make sure that the XHR is only done once per document
        if (doc.getUserData(lockKey) !== null) return;
        doc.setUserData(lockKey, true, null);
        req.open('HEAD', url, true);
        req.onreadystatechange = function(evt) {
            if (req.readyState === 4) {
                doc.setUserData(lockKey, null, null);
                if (req.status < 400 && req.status >= 200) {
                    callback(url);
                } else {
                    callback(false);
                }
            }
        };
        req.send(null);
    }

    function handleBtn(evt) {
        gBrowser.selectedTab = gBrowser.addTab(getTechUrl(window.content.document));
    }

    /**
     * @return any cached tech url in the current document
     */
    function getTechUrl(doc) {
        return doc.getUserData('techtxt-url');
    }

    /**
     * Caches the given URL in the current document.
     *
     * @param url The found url to be cached for the current document
     */
    function setTechUrl(doc, url) {
        doc.setUserData('techtxt-url', url, null);
    }

    /**
     * Executed whenever the current tab or its content changes in order
     * to update the status icon in the address bar.
     *
     * Once this is executed on a document, getTechUrl() will return any
     * found tech.txt.
     */
    function handlePageLoad(evt) {
        var tab = gBrowser.selectedTab,
            doc = window.content.document;
        if (window.content.location.protocol === 'about:') {
            icon.setAttribute('hidden', true);
            return;
        }
        findUrl(window, doc, tab, function(url, window_, doc_, tab_) {
            if (tab_ !== tab) {
                return;
            }
            if (url !== null && url !== false) {
                icon.setAttribute('hidden', false);
            } else {
                icon.setAttribute('hidden', true);
            }
        });
    }

    function init() {
        if (initialized) {
            return;
        }
        document.getElementById('techtxt-icon').setAttribute('hidden', true);
        appcontent = document.getElementById('appcontent');
        //appcontent.addEventListener('pageshow', handlePageLoad, false);
        gBrowser.addEventListener('DOMContentLoaded', handlePageLoad, false);
        gBrowser.tabContainer.addEventListener('TabSelect', handlePageLoad, false);
        icon = document.getElementById('techtxt-icon');
    }
    window.addEventListener('load', init, false);
    return publicFunctions;
})();
<commit_msg>Fixed an issue with the extension only recognizing tech.txt via the link-method if it is the first link in <head><commit_after>var techtxtdetector = (function() {
    var appcontent = null,
        initialized = false,
        icon = null,
        prefs = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefBranch).getBranch('techtxtdetector.');

    var publicFunctions = {
        'handleBtn': handleBtn
    };

    /**
     * Wrapper method for all findUrl* methods. Once a suitable method
     * returns an URL or no tech.txt is found at all, callback is executed
     * with the URL or "undefined" as argument.
     *
     */
    function findUrl(window, doc, tab, callback) {
        var url = getTechUrl(doc);
        if (url === null) {
            if (prefs.getBoolPref('methods.headlink') === true) {
                url = findUrlInDom(window.content.document);
            }
            if ((url === null || url === false) && prefs.getBoolPref('methods.domainfile') === true) {
                findUrlOnDomain(doc, function(foundUrl) {
                    setTechUrl(doc, foundUrl);
                    callback(foundUrl, window, doc, tab);
                });
                return;
            }
            setTechUrl(doc, url);
        }
        callback(url, window, doc, tab);
    }

    /**
     * Checks the DOM for a reference to a tech.txt file and returns it if
     * found. Otherwise false is returned.
     */
    function findUrlInDom(doc) {
        var links = doc.querySelectorAll('head>link'),
            linkCnt = links.length,
            link = null;
        for (var i = 0; i < linkCnt; i++) {
            link = links[i];
            if (link.getAttribute('rel') === 'tech.txt') {
                return link.getAttribute('href');
            }
        }
        return false;
    }

    function findUrlOnDomain(doc, callback) {
        var loc = window.content.location,
            url = loc.protocol + '//' + loc.host + '/tech.txt',
            req = new XMLHttpRequest(),
            lockKey = 'techtxt-lookup-in-progress';
        // Don't do a lookup on file://
        if (loc.protocol === 'file:') return callback(false);
        // Make sure that the XHR is only done once per document
        if (doc.getUserData(lockKey) !== null) return;
        doc.setUserData(lockKey, true, null);
        req.open('HEAD', url, true);
        req.onreadystatechange = function(evt) {
            if (req.readyState === 4) {
                doc.setUserData(lockKey, null, null);
                if (req.status < 400 && req.status >= 200) {
                    callback(url);
                } else {
                    callback(false);
                }
            }
        };
        req.send(null);
    }

    function handleBtn(evt) {
        gBrowser.selectedTab = gBrowser.addTab(getTechUrl(window.content.document));
    }

    /**
     * @return any cached tech url in the current document
     */
    function getTechUrl(doc) {
        return doc.getUserData('techtxt-url');
    }

    /**
     * Caches the given URL in the current document.
     *
     * @param url The found url to be cached for the current document
     */
    function setTechUrl(doc, url) {
        doc.setUserData('techtxt-url', url, null);
    }

    /**
     * Executed whenever the current tab or its content changes in order
     * to update the status icon in the address bar.
     *
     * Once this is executed on a document, getTechUrl() will return any
     * found tech.txt.
     */
    function handlePageLoad(evt) {
        var tab = gBrowser.selectedTab,
            doc = window.content.document;
        if (window.content.location.protocol === 'about:') {
            icon.setAttribute('hidden', true);
            return;
        }
        findUrl(window, doc, tab, function(url, window_, doc_, tab_) {
            if (tab_ !== tab) {
                return;
            }
            if (url !== null && url !== false) {
                icon.setAttribute('hidden', false);
            } else {
                icon.setAttribute('hidden', true);
            }
        });
    }

    function init() {
        if (initialized) {
            return;
        }
        document.getElementById('techtxt-icon').setAttribute('hidden', true);
        appcontent = document.getElementById('appcontent');
        //appcontent.addEventListener('pageshow', handlePageLoad, false);
        gBrowser.addEventListener('DOMContentLoaded', handlePageLoad, false);
        gBrowser.tabContainer.addEventListener('TabSelect', handlePageLoad, false);
        icon = document.getElementById('techtxt-icon');
    }
    window.addEventListener('load', init, false);
    return publicFunctions;
})();
<|endoftext|>


<commit_before>function makeUrl() {
 document.getElementById('fFile').innerHTML = '<input type="url" id="urlT">';
 document.getElementById('dRC').innerHTML = 'Download';
}

function mpCutI() {
 document.getElementById('display').innerHTML = 'Browser Not Supported

Mp3 Conversion By mp3cut.net

Not a fan of your website in my iFrames?
Email Me At darkheart527s@gmail.com with proof of ownership
';
}

function uUrl() {
 document.getElementById('urlA').href = document.getElementById('urlT').value;
}
<commit_msg>Update convertJava.js<commit_after>function makeUrl() {
 document.getElementById('fFile').innerHTML = '<input type="url" id="urlT">';
 document.getElementById('dRC').innerHTML = 'Download';
}

function mpCutI() {
 document.getElementById('display').innerHTML = 'Browser Not Supported

Mp3 Conversion By mp3cut.net

Not a fan of your website in my iFrames?
Email Me At darkheart527s@gmail.com with proof of ownership
';
}

function uUrl() {
 document.getElementById('urlA').href = document.getElementById('urlT').value;
}
<|endoftext|>