<commit_before>"use strict";
define(['jquery', 'underscore', 'chat/user', 'chat/url'], function ($, _, User, url) {

/**
 * @exports Logger
 */

/**
 * @returns a String in format HH:mm:ss (local time) from a a UTC timestamp (seconds since 1970)
 */
function prettyTime(timestamp) {
	var time = new Date(timestamp * 1000);
	time = [time.getHours(), time.getMinutes(), time.getSeconds()]
	return _.map(time, function (e) {
		return e < 10 ? "0" + e : e;
	}).join(":");
}

/**
 * @constructor
 * @param {HTML-Entity} ul - The ul where to log to.
 */
function Logger(ul) {
	this.ul = $(ul);
	this._buffer = [];
	this._bufferWatchTimer = null;
	this._newMessageId();
}

/**
 * @private
 * @returns {String} a new unique message ID.
 */
Logger.prototype._newMessageId = function () {
	// We're using random to obscure how many messages have been sent.
	// It's probably a silly feature, but it doesn't cost much, so why not.
	var stepSize = 1000; // stepSize is equal to the amount of fuzziness in determining the amount of sent messages.
	if (!_.isFinite(this._messageId)) {
		// Start with a random offset so we have some kind of plausible deniability.
		this._messageId = _.random(_.random(stepSize, 2 * stepSize), stepSize * stepSize);
	}
	var newId = this._messageId + _.random(1, stepSize);
	this._messageId = newId + _.random(1, stepSize); // Do another step so the newId isn't stored permanently.
	return newId.toString();
};

/**
 * Adds a tag (HTML class) to a message.
 * @param {String} msgId - The message's unique ID.
 * @param {Array|String} tags - Array or `.`-separated list of tags.
 */
Logger.prototype.addTags = function (msgId, tags) {
	if (_.isString(tags)) {
		tags = tags.split(".");
	}
	var msg = this.msgElement(msgId);
	_.each(tags, function (tag) {
		msg.addClass(tag);
	});
};

/**
 * Removes a tag (HTML class) from a message.
 * @param {String} msgId - The message's unique ID.
 * @param {String[]|String} tags - Array or `.`-separated list of tags.
 */
Logger.prototype.removeTags = function (msgId, tags) {
	if (_.isString(tags)) {
		tags = tags.split(".");
	}
	var msg = this.msgElement(msgId);
	_.each(tags, function (tag) {
		msg.removeClass(tag);
	});
};

/**
 * @returns {Boolean} true if the buffer was successfully emptied, otherwise false.
 */
Logger.prototype._dumpBuffer = function () {
	var list = this.ul, buffer = this._buffer;
	if (buffer.length) {
		if (!list.length) {
			return false;
		} else {
			_.each(buffer, function (newLine) {
				list.append(newLine);
			});
			this._buffer = [];
		}
	}
	return true;
};

/**
 * Keeps a timer running, checking that the line buffer is emptied as soon as possible.
 */
Logger.prototype._watchBuffer = function () {
	if (_.isNull(this._bufferWatchTimer) && !this._dumpBuffer()) {
		this._bufferWatchTimer = setTimeout(_.bind(function () {
			this._bufferWatchTimer = null;
			this._watchBuffer();
		}, this), 100);
	}
};

/**
 * Add a new message to the log.
 * Automatically scrolls to the latest message unless told not to do so.
 * @param {Object|Messager.Message|String} message - Messager.Message compatible Object or text.
 * @param {Boolean} [dontScroll] - Set to true if you're logging a huge amount of messages at once.
 * @returns {String} the new message's id.
 */
Logger.prototype.log = function (message, dontScroll) {
	if (_.isString(message)) {
		message = {text: message};
	}
	// check for User
	if (message.from instanceof User) {
		var user = message.from;
		message.from = user.nick;
		message.signature = user.signature;
		message.color = user.color;
	}
	// create new line
	var msgId = message.msgId || this._newMessageId();
	var newLine = $("<li>", {
		"class": "message",
		id: "msg-" + msgId
	});
	// Attach the new line to the DOM or buffer it if no ul is set.
	if (!this.ul.length) {
		this._buffer.push(newLine);
		this._watchBuffer();
	} else {
		// Push out buffered lines first.
		this._dumpBuffer();
		this.ul.append(newLine);
	}
	// add line content
	var lineParts = [
		["timestamp", prettyTime(message.timestamp || parseInt(Date.now() / 1000))],
		["from", message.from],
		["text", message.text]
	];
	_.each(lineParts, function (e, i) {
		var name = e[0], value = e[1];
		if (value) {
			lineParts[i][1] = $("<span>", {"class": name})
				.text(value)
				.appendTo(newLine);
		}
	});
	lineParts = _.object(lineParts);
	// enhance timestamp
	var timediff = parseInt(message.timestamp - message.serverTimestamp) + 5;
	if (timediff) {
		lineParts.timestamp.attr('title', 'desynchronized by ' + timediff + 's');
		if (Math.abs(timediff) > 3) {
			lineParts.timestamp.addClass('warning');
		}
	}
	// create text links
	lineParts.text.html(url.replaceUrlsWithHtmlLinks(lineParts.text.html()));
	// add nick color
	message.color = message.color || User.calculateColor(message.from, message.signature);
	if (message.color) {
		lineParts.from.css('color', message.color);
	}
	// add tags
	this.addTags(msgId, message.tags);
	//
	if (!dontScroll) {
		this.scrollToLatest();
	}
	// done.
	return msgId;
};

Logger.prototype.removeMessage = function (msgId) {
	this.msgElement(msgId).remove();
}

Logger.prototype.msgElement = function(msgId) {
	return $("#msg-" + msgId);
}
<commit_msg>bugfix.<commit_after>"use strict";
define(['jquery', 'underscore', 'chat/user', 'chat/url'], function ($, _, User, url) {

/**
 * @exports Logger
 */

/**
 * @returns a String in format HH:mm:ss (local time) from a a UTC timestamp (seconds since 1970)
 */
function prettyTime(timestamp) {
	var time = new Date(timestamp * 1000);
	time = [time.getHours(), time.getMinutes(), time.getSeconds()]
	return _.map(time, function (e) {
		return e < 10 ? "0" + e : e;
	}).join(":");
}

/**
 * @constructor
 * @param {HTML-Entity} ul - The ul where to log to.
 */
function Logger(ul) {
	this.ul = $(ul);
	this._buffer = [];
	this._bufferWatchTimer = null;
	this._newMessageId();
}

/**
 * @private
 * @returns {String} a new unique message ID.
 */
Logger.prototype._newMessageId = function () {
	// We're using random to obscure how many messages have been sent.
	// It's probably a silly feature, but it doesn't cost much, so why not.
	var stepSize = 1000; // stepSize is equal to the amount of fuzziness in determining the amount of sent messages.
	if (!_.isFinite(this._messageId)) {
		// Start with a random offset so we have some kind of plausible deniability.
		this._messageId = _.random(_.random(stepSize, 2 * stepSize), stepSize * stepSize);
	}
	var newId = this._messageId + _.random(1, stepSize);
	this._messageId = newId + _.random(1, stepSize); // Do another step so the newId isn't stored permanently.
	return newId.toString();
};

/**
 * Adds a tag (HTML class) to a message.
 * @param {String} msgId - The message's unique ID.
 * @param {Array|String} tags - Array or `.`-separated list of tags.
 */
Logger.prototype.addTags = function (msgId, tags) {
	if (_.isString(tags)) {
		tags = tags.split(".");
	}
	var msg = this.msgElement(msgId);
	_.each(tags, function (tag) {
		msg.addClass(tag);
	});
};

/**
 * Removes a tag (HTML class) from a message.
 * @param {String} msgId - The message's unique ID.
 * @param {String[]|String} tags - Array or `.`-separated list of tags.
 */
Logger.prototype.removeTags = function (msgId, tags) {
	if (_.isString(tags)) {
		tags = tags.split(".");
	}
	var msg = this.msgElement(msgId);
	_.each(tags, function (tag) {
		msg.removeClass(tag);
	});
};

/**
 * @returns {Boolean} true if the buffer was successfully emptied, otherwise false.
 */
Logger.prototype._dumpBuffer = function () {
	var list = this.ul, buffer = this._buffer;
	if (buffer.length) {
		if (!list.length) {
			return false;
		} else {
			_.each(buffer, function (newLine) {
				list.append(newLine);
			});
			this._buffer = [];
		}
	}
	return true;
};

/**
 * Keeps a timer running, checking that the line buffer is emptied as soon as possible.
 */
Logger.prototype._watchBuffer = function () {
	if (_.isNull(this._bufferWatchTimer) && !this._dumpBuffer()) {
		this._bufferWatchTimer = setTimeout(_.bind(function () {
			this._bufferWatchTimer = null;
			this._watchBuffer();
		}, this), 100);
	}
};

/**
 * Add a new message to the log.
 * Automatically scrolls to the latest message unless told not to do so.
 * @param {Object|Messager.Message|String} message - Messager.Message compatible Object or text.
 * @param {Boolean} [dontScroll] - Set to true if you're logging a huge amount of messages at once.
 * @returns {String} the new message's id.
 */
Logger.prototype.log = function (message, dontScroll) {
	if (_.isString(message)) {
		message = {text: message};
	}
	// check for User
	if (message.from instanceof User) {
		var user = message.from;
		message.from = user.nick;
		message.signature = user.signature;
		message.color = user.color;
	}
	// create new line
	var msgId = message.msgId || this._newMessageId();
	var newLine = $("<li>", {
		"class": "message",
		id: "msg-" + msgId
	});
	// Attach the new line to the DOM or buffer it if no ul is set.
	if (!this.ul.length) {
		this._buffer.push(newLine);
		this._watchBuffer();
	} else {
		// Push out buffered lines first.
		this._dumpBuffer();
		this.ul.append(newLine);
	}
	// add line content
	var lineParts = [
		["timestamp", prettyTime(message.timestamp || parseInt(Date.now() / 1000))],
		["from", message.from],
		["text", message.text]
	];
	_.each(lineParts, function (e, i) {
		var name = e[0], value = e[1];
		if (value) {
			lineParts[i][1] = $("<span>", {"class": name})
				.text(value)
				.appendTo(newLine);
		}
	});
	lineParts = _.object(lineParts);
	// add desynchronization delta to timestamp
	var timediff = parseInt(message.timestamp - message.serverTimestamp);
	if (lineParts.timestamp && timediff) {
		lineParts.timestamp.attr('title', 'desynchronized by ' + timediff + 's');
		if (Math.abs(timediff) > 3) {
			lineParts.timestamp.addClass('warning');
		}
	}
	// create text links
	if (lineParts.text) {
		lineParts.text.html(url.replaceUrlsWithHtmlLinks(lineParts.text.html()));
	}
	// add nick color
	message.color = message.color || User.calculateColor(message.from, message.signature);
	if (message.color && lineParts.from) {
		lineParts.from.css('color', message.color);
	}
	// add tags
	this.addTags(msgId, message.tags);
	//
	if (!dontScroll) {
		this.scrollToLatest();
	}
	// done.
	return msgId;
};

Logger.prototype.removeMessage = function (msgId) {
	this.msgElement(msgId).remove();
}

Logger.prototype.msgElement = function(msgId) {
	return $("#msg-" + msgId);
}

{'fraction_non_alphanumeric': 0.16028639618138424, 'fraction_numerical': 0.005632458233890214, 'mean_word_length': 3.6539315859617947, 'pattern_counts': {'":': 6, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6409158', 'n_tokens_mistral': 3677, 'n_tokens_neox': 3475, 'n_words': 1000}