Files
Bubberstation/code/modules/goonchat/browserassets/js/browserOutput.js
nemvar c40b6b09d7 Refocus the map after clicking a goonchat link (#51104)
Clicking a goonchat link will now instantly return focus to the game 
map. This means that the game will no longer eat your next input after 
you clicked a link.

This makes the game feel more responsive and with no downside. Also 
removes an ugly function that was supposed to implement this feature. 
This function was also causing some other issues, for example, it could 
cause you to take a screenshot with f2 although you have rebound it.
2020-05-15 00:55:12 -07:00

1075 lines
34 KiB
JavaScript

/*****************************************
*
* FUNCTION AND VAR DECLARATIONS
*
******************************************/
//DEBUG STUFF
var escaper = encodeURIComponent || escape;
var decoder = decodeURIComponent || unescape;
window.onerror = function(msg, url, line, col, error) {
if (document.location.href.indexOf("proc=debug") <= 0) {
var extra = !col ? '' : ' | column: ' + col;
extra += !error ? '' : ' | error: ' + error;
extra += !navigator.userAgent ? '' : ' | user agent: ' + navigator.userAgent;
var debugLine = 'Error: ' + msg + ' | url: ' + url + ' | line: ' + line + extra;
window.location = '?_src_=chat&proc=debug&param[error]='+escaper(debugLine);
}
return true;
};
//Globals
window.status = 'Output';
var $messages, $subOptions, $subAudio, $selectedSub, $contextMenu, $filterMessages, $last_message;
var opts = {
//General
'messageCount': 0, //A count...of messages...
'messageLimit': 2053, //A limit...for the messages...
'scrollSnapTolerance': 10, //If within x pixels of bottom
'clickTolerance': 10, //Keep focus if outside x pixels of mousedown position on mouseup
'imageRetryDelay': 50, //how long between attempts to reload images (in ms)
'imageRetryLimit': 50, //how many attempts should we make?
'popups': 0, //Amount of popups opened ever
'wasd': false, //Is the user in wasd mode?
'priorChatHeight': 0, //Thing for height-resizing detection
'restarting': false, //Is the round restarting?
'darkmode':false, //Are we using darkmode? If not WHY ARE YOU LIVING IN 2009???
//Options menu
'selectedSubLoop': null, //Contains the interval loop for closing the selected sub menu
'suppressSubClose': false, //Whether or not we should be hiding the selected sub menu
'highlightTerms': [],
'highlightLimit': 5,
'highlightColor': '#FFFF00', //The color of the highlighted message
'pingDisabled': false, //Has the user disabled the ping counter
//Ping display
'lastPang': 0, //Timestamp of the last response from the server.
'pangLimit': 35000,
'pingTime': 0, //Timestamp of when ping sent
'pongTime': 0, //Timestamp of when ping received
'noResponse': false, //Tracks the state of the previous ping request
'noResponseCount': 0, //How many failed pings?
//Clicks
'mouseDownX': null,
'mouseDownY': null,
'preventFocus': false, //Prevents switching focus to the game window
//Client Connection Data
'clientDataLimit': 5,
'clientData': [],
//Admin music volume update
'volumeUpdateDelay': 5000, //Time from when the volume updates to data being sent to the server
'volumeUpdating': false, //True if volume update function set to fire
'updatedVolume': 0, //The volume level that is sent to the server
'musicStartAt': 0, //The position the music starts playing
'musicEndAt': 0, //The position the music... stops playing... if null, doesn't apply (so the music runs through)
'defaultMusicVolume': 25,
'messageCombining': true,
};
var replaceRegexes = {};
function clamp(val, min, max) {
return Math.max(min, Math.min(val, max))
}
//Polyfill for fucking date now because of course IE8 and below don't support it
if (!Date.now) {
Date.now = function now() {
return new Date().getTime();
};
}
//Polyfill for trim() (IE8 and below)
if (typeof String.prototype.trim !== 'function') {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, '');
};
}
// Linkify the contents of a node, within its parent.
function linkify(parent, insertBefore, text) {
var start = 0;
var match;
var regex = /(?:(?:https?:\/\/)|(?:www\.))(?:[^ ]*?\.[^ ]*?)+[-A-Za-z0-9+&@#\/%?=~_|$!:,.;()]+/ig;
while ((match = regex.exec(text)) !== null) {
// add the unmatched text
parent.insertBefore(document.createTextNode(text.substring(start, match.index)), insertBefore);
var href = match[0];
if (!/^https?:\/\//i.test(match[0])) {
href = "http://" + match[0];
}
// add the link
var link = document.createElement("a");
link.href = href;
link.textContent = match[0];
parent.insertBefore(link, insertBefore);
start = regex.lastIndex;
}
if (start !== 0) {
// add the remaining text and remove the original text node
parent.insertBefore(document.createTextNode(text.substring(start)), insertBefore);
parent.removeChild(insertBefore);
}
}
// Recursively linkify the children of a given node.
function linkify_node(node) {
var children = node.childNodes;
// work backwards to avoid the risk of looping forever on our own output
for (var i = children.length - 1; i >= 0; --i) {
var child = children[i];
if (child.nodeType == Node.TEXT_NODE) {
// text is to be linkified
linkify(node, child, child.textContent);
} else if (child.nodeName != "A" && child.nodeName != "a") {
// do not linkify existing links
linkify_node(child);
}
}
}
//Shit fucking piece of crap that doesn't work god fuckin damn it
function linkify_fallback(text) {
var rex = /((?:<a|<iframe|<img)(?:.*?(?:src="|href=").*?))?(?:(?:https?:\/\/)|(?:www\.))+(?:[^ ]*?\.[^ ]*?)+[-A-Za-z0-9+&@#\/%?=~_|$!:,.;]+/ig;
return text.replace(rex, function ($0, $1) {
if(/^https?:\/\/.+/i.test($0)) {
return $1 ? $0: '<a href="'+$0+'">'+$0+'</a>';
}
else {
return $1 ? $0: '<a href="http://'+$0+'">'+$0+'</a>';
}
});
}
function byondDecode(message) {
// Basically we url_encode twice server side so we can manually read the encoded version and actually do UTF-8.
// The replace for + is because FOR SOME REASON, BYOND replaces spaces with a + instead of %20, and a plus with %2b.
// Marvelous.
message = message.replace(/\+/g, "%20");
try {
// This is a workaround for the above not always working when BYOND's shitty url encoding breaks. (byond bug id:2399401)
if (decodeURIComponent) {
message = decodeURIComponent(message);
} else {
throw new Error("Easiest way to trigger the fallback")
}
} catch (err) {
message = unescape(message);
}
return message;
}
function replaceRegex() {
var selectedRegex = replaceRegexes[$(this).attr('replaceRegex')];
if (selectedRegex) {
var replacedText = $(this).html().replace(selectedRegex[0], selectedRegex[1]);
$(this).html(replacedText);
}
$(this).removeAttr('replaceRegex');
}
// Get a highlight markup span
function createHighlightMarkup() {
var extra = '';
if (opts.highlightColor) {
extra += ' style="background-color: ' + opts.highlightColor + '"';
}
return '<span class="highlight"' + extra + '></span>';
}
// Get all child text nodes that match a regex pattern
function getTextNodes(elem, pattern) {
var result = $([]);
$(elem).contents().each(function(idx, child) {
if (child.nodeType === 3 && /\S/.test(child.nodeValue) && pattern.test(child.nodeValue)) {
result = result.add(child);
}
else {
result = result.add(getTextNodes(child, pattern));
}
});
return result;
}
// Highlight all text terms matching the registered regex patterns
function highlightTerms(el) {
var pattern = new RegExp("(" + opts.highlightTerms.join('|') + ")", 'gi');
var nodes = getTextNodes(el, pattern);
nodes.each(function (idx, node) {
var content = $(node).text();
var parent = $(node).parent();
var pre = $(node.previousSibling);
$(node).remove();
content.split(pattern).forEach(function (chunk) {
// Get our highlighted span/text node
var toInsert = null;
if (pattern.test(chunk)) {
var tmpElem = $(createHighlightMarkup());
tmpElem.text(chunk);
toInsert = tmpElem;
}
else {
toInsert = document.createTextNode(chunk);
}
// Insert back into our element
if (pre.length == 0) {
var result = parent.prepend(toInsert);
pre = $(result[0].firstChild);
}
else {
pre.after(toInsert);
pre = $(pre[0].nextSibling);
}
});
});
}
function iconError(E) {
var that = this;
setTimeout(function() {
var attempts = $(that).data('reload_attempts');
if (typeof attempts === 'undefined' || !attempts) {
attempts = 1;
}
if (attempts > opts.imageRetryLimit)
return;
var src = that.src;
that.src = null;
that.src = src+'#'+attempts;
$(that).data('reload_attempts', ++attempts);
}, opts.imageRetryDelay);
}
//Send a message to the client
function output(message, flag) {
if (typeof message === 'undefined') {
return;
}
if (typeof flag === 'undefined') {
flag = '';
}
if (flag !== 'internal')
opts.lastPang = Date.now();
message = byondDecode(message).trim();
//The behemoth of filter-code (for Admin message filters)
//Note: This is proooobably hella inefficient
var filteredOut = false;
if (opts.hasOwnProperty('showMessagesFilters') && !opts.showMessagesFilters['All'].show) {
//Get this filter type (defined by class on message)
var messageHtml = $.parseHTML(message),
messageClasses;
if (opts.hasOwnProperty('filterHideAll') && opts.filterHideAll) {
var internal = false;
messageClasses = (!!$(messageHtml).attr('class') ? $(messageHtml).attr('class').split(/\s+/) : false);
if (messageClasses) {
for (var i = 0; i < messageClasses.length; i++) { //Every class
if (messageClasses[i] == 'internal') {
internal = true;
break;
}
}
}
if (!internal) {
filteredOut = 'All';
}
} else {
//If the element or it's child have any classes
if (!!$(messageHtml).attr('class') || !!$(messageHtml).children().attr('class')) {
messageClasses = $(messageHtml).attr('class').split(/\s+/);
if (!!$(messageHtml).children().attr('class')) {
messageClasses = messageClasses.concat($(messageHtml).children().attr('class').split(/\s+/));
}
var tempCount = 0;
for (var i = 0; i < messageClasses.length; i++) { //Every class
var thisClass = messageClasses[i];
$.each(opts.showMessagesFilters, function(key, val) { //Every filter
if (key !== 'All' && val.show === false && typeof val.match != 'undefined') {
for (var i = 0; i < val.match.length; i++) {
var matchClass = val.match[i];
if (matchClass == thisClass) {
filteredOut = key;
break;
}
}
}
if (filteredOut) return false;
});
if (filteredOut) break;
tempCount++;
}
} else {
if (!opts.showMessagesFilters['Misc'].show) {
filteredOut = 'Misc';
}
}
}
}
//Stuff we do along with appending a message
var atBottom = false;
if (!filteredOut) {
var bodyHeight = $('body').height();
var messagesHeight = $messages.outerHeight();
var scrollPos = $('body,html').scrollTop();
//Should we snap the output to the bottom?
if (bodyHeight + scrollPos >= messagesHeight - opts.scrollSnapTolerance) {
atBottom = true;
if ($('#newMessages').length) {
$('#newMessages').remove();
}
//If not, put the new messages box in
} else {
if ($('#newMessages').length) {
var messages = $('#newMessages .number').text();
messages = parseInt(messages);
messages++;
$('#newMessages .number').text(messages);
if (messages == 2) {
$('#newMessages .messageWord').append('s');
}
} else {
$messages.after('<a href="#" id="newMessages"><span class="number">1</span> new <span class="messageWord">message</span> <i class="icon-double-angle-down"></i></a>');
}
}
}
opts.messageCount++;
//Pop the top message off if history limit reached
if (opts.messageCount >= opts.messageLimit) {
$messages.children('div.entry:first-child').remove();
opts.messageCount--; //I guess the count should only ever equal the limit
}
// Create the element - if combining is off, we use it, and if it's on, we
// might discard it bug need to check its text content. Some messages vary
// only in HTML markup, have the same text content, and should combine.
var entry = document.createElement('div');
entry.innerHTML = message;
var trimmed_message = entry.textContent || entry.innerText || "";
var handled = false;
if (opts.messageCombining) {
var lastmessages = $messages.children('div.entry:last-child').last();
if (lastmessages.length && $last_message && $last_message == trimmed_message) {
var badge = lastmessages.children('.r').last();
if (badge.length) {
badge = badge.detach();
badge.text(parseInt(badge.text()) + 1);
} else {
badge = $('<span/>', {'class': 'r', 'text': 2});
}
lastmessages.html(message);
lastmessages.find('[replaceRegex]').each(replaceRegex);
lastmessages.append(badge);
badge.animate({
"font-size": "0.9em"
}, 100, function() {
badge.animate({
"font-size": "0.7em"
}, 100);
});
opts.messageCount--;
handled = true;
}
}
if (!handled) {
//Actually append the message
entry.className = 'entry';
if (filteredOut) {
entry.className += ' hidden';
entry.setAttribute('data-filter', filteredOut);
}
$(entry).find('[replaceRegex]').each(replaceRegex);
$last_message = trimmed_message;
$messages[0].appendChild(entry);
$(entry).find("img.icon").error(iconError);
var to_linkify = $(entry).find(".linkify");
if (typeof Node === 'undefined') {
// Linkify fallback for old IE
for(var i = 0; i < to_linkify.length; ++i) {
to_linkify[i].innerHTML = linkify_fallback(to_linkify[i].innerHTML);
}
} else {
// Linkify for modern IE versions
for(var i = 0; i < to_linkify.length; ++i) {
linkify_node(to_linkify[i]);
}
}
//Actually do the snap
//Stuff we can do after the message shows can go here, in the interests of responsiveness
if (opts.highlightTerms && opts.highlightTerms.length > 0) {
highlightTerms($(entry));
}
}
if (!filteredOut && atBottom) {
$('body,html').scrollTop($messages.outerHeight());
}
}
function internalOutput(message, flag)
{
output(escaper(message), flag)
}
//Runs a route within byond, client or server side. Consider this "ehjax" for byond.
function runByond(uri) {
window.location = uri;
}
function setCookie(cname, cvalue, exdays) {
cvalue = escaper(cvalue);
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = 'expires='+d.toUTCString();
document.cookie = cname + '=' + cvalue + '; ' + expires + "; path=/";
}
function getCookie(cname) {
var name = cname + '=';
var ca = document.cookie.split(';');
for(var i=0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1);
if (c.indexOf(name) === 0) {
return decoder(c.substring(name.length,c.length));
}
}
return '';
}
function rgbToHex(R,G,B) {return toHex(R)+toHex(G)+toHex(B);}
function toHex(n) {
n = parseInt(n,10);
if (isNaN(n)) return "00";
n = Math.max(0,Math.min(n,255));
return "0123456789ABCDEF".charAt((n-n%16)/16) + "0123456789ABCDEF".charAt(n%16);
}
function swap() { //Swap to darkmode
if (opts.darkmode){
document.getElementById("sheetofstyles").href = "browserOutput_white.css";
opts.darkmode = false;
runByond('?_src_=chat&proc=swaptolightmode');
} else {
document.getElementById("sheetofstyles").href = "browserOutput.css";
opts.darkmode = true;
runByond('?_src_=chat&proc=swaptodarkmode');
}
setCookie('darkmode', (opts.darkmode ? 'true' : 'false'), 365);
}
function handleClientData(ckey, ip, compid) {
//byond sends player info to here
var currentData = {'ckey': ckey, 'ip': ip, 'compid': compid};
if (opts.clientData && !$.isEmptyObject(opts.clientData)) {
runByond('?_src_=chat&proc=analyzeClientData&param[cookie]='+JSON.stringify({'connData': opts.clientData}));
for (var i = 0; i < opts.clientData.length; i++) {
var saved = opts.clientData[i];
if (currentData.ckey == saved.ckey && currentData.ip == saved.ip && currentData.compid == saved.compid) {
return; //Record already exists
}
}
//Lets make sure we obey our limit (can connect from server with higher limit)
while (opts.clientData.length >= opts.clientDataLimit) {
opts.clientData.shift();
}
} else {
runByond('?_src_=chat&proc=analyzeClientData&param[cookie]=none');
}
//Update the cookie with current details
opts.clientData.push(currentData);
setCookie('connData', JSON.stringify(opts.clientData), 365);
}
//Server calls this on ehjax response
//Or, y'know, whenever really
function ehjaxCallback(data) {
opts.lastPang = Date.now();
if (data == 'softPang') {
return;
} else if (data == 'pang') {
opts.pingCounter = 0; //reset
opts.pingTime = Date.now();
runByond('?_src_=chat&proc=ping');
} else if (data == 'pong') {
if (opts.pingDisabled) {return;}
opts.pongTime = Date.now();
var pingDuration = Math.ceil((opts.pongTime - opts.pingTime) / 2);
$('#pingMs').text(pingDuration+'ms');
pingDuration = Math.min(pingDuration, 255);
var red = pingDuration;
var green = 255 - pingDuration;
var blue = 0;
var hex = rgbToHex(red, green, blue);
$('#pingDot').css('color', '#'+hex);
} else if (data == 'roundrestart') {
opts.restarting = true;
internalOutput('<div class="connectionClosed internal restarting">The connection has been closed because the server is restarting. Please wait while you automatically reconnect.</div>', 'internal');
} else if (data == 'stopMusic') {
$('#adminMusic').prop('src', '');
} else {
//Oh we're actually being sent data instead of an instruction
var dataJ;
try {
dataJ = $.parseJSON(data);
} catch (e) {
//But...incorrect :sadtrombone:
window.onerror('JSON: '+e+'. '+data, 'browserOutput.html', 327);
return;
}
data = dataJ;
if (data.clientData) {
if (opts.restarting) {
opts.restarting = false;
$('.connectionClosed.restarting:not(.restored)').addClass('restored').text('The round restarted and you successfully reconnected!');
}
if (!data.clientData.ckey && !data.clientData.ip && !data.clientData.compid) {
//TODO: Call shutdown perhaps
return;
} else {
handleClientData(data.clientData.ckey, data.clientData.ip, data.clientData.compid);
}
sendVolumeUpdate();
} else if (data.adminMusic) {
if (typeof data.adminMusic === 'string') {
var adminMusic = byondDecode(data.adminMusic);
var bindLoadedData = false;
adminMusic = adminMusic.match(/https?:\/\/\S+/) || '';
if (data.musicRate) {
var newRate = Number(data.musicRate);
if(newRate) {
$('#adminMusic').prop('defaultPlaybackRate', newRate);
}
} else {
$('#adminMusic').prop('defaultPlaybackRate', 1.0);
}
if (data.musicSeek) {
opts.musicStartAt = Number(data.musicSeek) || 0;
bindLoadedData = true;
} else {
opts.musicStartAt = 0;
}
if (data.musicHalt) {
opts.musicEndAt = Number(data.musicHalt) || null;
bindLoadedData = true;
}
if (bindLoadedData) {
$('#adminMusic').one('loadeddata', adminMusicLoadedData);
}
$('#adminMusic').prop('src', adminMusic);
$('#adminMusic').trigger("play");
}
} else if (data.syncRegex) {
for (var i in data.syncRegex) {
var regexData = data.syncRegex[i];
var regexName = regexData[0];
var regexFlags = regexData[1];
var regexReplaced = regexData[2];
replaceRegexes[i] = [new RegExp(regexName, regexFlags), regexReplaced];
}
}
}
}
function createPopup(contents, width) {
opts.popups++;
$('body').append('<div class="popup" id="popup'+opts.popups+'" style="width: '+width+'px;">'+contents+' <a href="#" class="close"><i class="icon-remove"></i></a></div>');
//Attach close popup event
var $popup = $('#popup'+opts.popups);
var height = $popup.outerHeight();
$popup.css({'height': height+'px', 'margin': '-'+(height/2)+'px 0 0 -'+(width/2)+'px'});
$popup.on('click', '.close', function(e) {
e.preventDefault();
$popup.remove();
});
}
function toggleWasd(state) {
opts.wasd = (state == 'on' ? true : false);
}
function sendVolumeUpdate() {
opts.volumeUpdating = false;
if(opts.updatedVolume) {
runByond('?_src_=chat&proc=setMusicVolume&param[volume]='+opts.updatedVolume);
}
}
function adminMusicEndCheck(event) {
if (opts.musicEndAt) {
if ($('#adminMusic').prop('currentTime') >= opts.musicEndAt) {
$('#adminMusic').off(event);
$('#adminMusic').trigger('pause');
$('#adminMusic').prop('src', '');
}
} else {
$('#adminMusic').off(event);
}
}
function adminMusicLoadedData(event) {
if (opts.musicStartAt && ($('#adminMusic').prop('duration') === Infinity || (opts.musicStartAt <= $('#adminMusic').prop('duration'))) ) {
$('#adminMusic').prop('currentTime', opts.musicStartAt);
}
if (opts.musicEndAt) {
$('#adminMusic').on('timeupdate', adminMusicEndCheck);
}
}
function subSlideUp() {
$(this).removeClass('scroll');
$(this).css('height', '');
}
function startSubLoop() {
if (opts.selectedSubLoop) {
clearInterval(opts.selectedSubLoop);
}
return setInterval(function() {
if (!opts.suppressSubClose && $selectedSub.is(':visible')) {
$selectedSub.slideUp('fast', subSlideUp);
clearInterval(opts.selectedSubLoop);
}
}, 5000); //every 5 seconds
}
function handleToggleClick($sub, $toggle) {
if ($selectedSub !== $sub && $selectedSub.is(':visible')) {
$selectedSub.slideUp('fast', subSlideUp);
}
$selectedSub = $sub
if ($selectedSub.is(':visible')) {
$selectedSub.slideUp('fast', subSlideUp);
clearInterval(opts.selectedSubLoop);
} else {
$selectedSub.slideDown('fast', function() {
var windowHeight = $(window).height();
var toggleHeight = $toggle.outerHeight();
var priorSubHeight = $selectedSub.outerHeight();
var newSubHeight = windowHeight - toggleHeight;
$(this).height(newSubHeight);
if (priorSubHeight > (windowHeight - toggleHeight)) {
$(this).addClass('scroll');
}
});
opts.selectedSubLoop = startSubLoop();
}
}
/*****************************************
*
* DOM READY
*
******************************************/
if (typeof $ === 'undefined') {
var div = document.getElementById('loading').childNodes[1];
div += '<br><br>ERROR: Jquery did not load.';
}
$(function() {
$messages = $('#messages');
$subOptions = $('#subOptions');
$subAudio = $('#subAudio');
$selectedSub = $subOptions;
//Hey look it's a controller loop!
setInterval(function() {
if (opts.lastPang + opts.pangLimit < Date.now() && !opts.restarting) { //Every pingLimit
if (!opts.noResponse) { //Only actually append a message if the previous ping didn't also fail (to prevent spam)
opts.noResponse = true;
opts.noResponseCount++;
internalOutput('<div class="connectionClosed internal" data-count="'+opts.noResponseCount+'">You are either AFK, experiencing lag or the connection has closed.</div>', 'internal');
}
} else if (opts.noResponse) { //Previous ping attempt failed ohno
$('.connectionClosed[data-count="'+opts.noResponseCount+'"]:not(.restored)').addClass('restored').text('Your connection has been restored (probably)!');
opts.noResponse = false;
}
}, 2000); //2 seconds
/*****************************************
*
* LOAD SAVED CONFIG
*
******************************************/
var savedConfig = {
fontsize: getCookie('fontsize'),
lineheight: getCookie('lineheight'),
'spingDisabled': getCookie('pingdisabled'),
'shighlightTerms': getCookie('highlightterms'),
'shighlightColor': getCookie('highlightcolor'),
'smusicVolume': getCookie('musicVolume'),
'smessagecombining': getCookie('messagecombining'),
'sdarkmode': getCookie('darkmode'),
};
if (savedConfig.fontsize) {
$messages.css('font-size', savedConfig.fontsize);
internalOutput('<span class="internal boldnshit">Loaded font size setting of: '+savedConfig.fontsize+'</span>', 'internal');
}
if (savedConfig.lineheight) {
$("body").css('line-height', savedConfig.lineheight);
internalOutput('<span class="internal boldnshit">Loaded line height setting of: '+savedConfig.lineheight+'</span>', 'internal');
}
if(savedConfig.sdarkmode == 'true'){
swap();
}
if (savedConfig.spingDisabled) {
if (savedConfig.spingDisabled == 'true') {
opts.pingDisabled = true;
$('#ping').hide();
}
internalOutput('<span class="internal boldnshit">Loaded ping display of: '+(opts.pingDisabled ? 'hidden' : 'visible')+'</span>', 'internal');
}
if (savedConfig.shighlightTerms) {
var savedTerms = $.parseJSON(savedConfig.shighlightTerms).filter(function (entry) {
return entry !== null && /\S/.test(entry);
});
var actualTerms = savedTerms.length != 0 ? savedTerms.join(', ') : null;
if (actualTerms) {
internalOutput('<span class="internal boldnshit">Loaded highlight strings of: ' + actualTerms+'</span>', 'internal');
opts.highlightTerms = savedTerms;
}
}
if (savedConfig.shighlightColor) {
opts.highlightColor = savedConfig.shighlightColor;
internalOutput('<span class="internal boldnshit">Loaded highlight color of: '+savedConfig.shighlightColor+'</span>', 'internal');
}
if (savedConfig.smusicVolume) {
var newVolume = clamp(savedConfig.smusicVolume, 0, 100);
$('#adminMusic').prop('volume', newVolume / 100);
$('#musicVolume').val(newVolume);
opts.updatedVolume = newVolume;
sendVolumeUpdate();
internalOutput('<span class="internal boldnshit">Loaded music volume of: '+savedConfig.smusicVolume+'</span>', 'internal');
}
else{
$('#adminMusic').prop('volume', opts.defaultMusicVolume / 100);
}
if (savedConfig.smessagecombining) {
if (savedConfig.smessagecombining == 'false') {
opts.messageCombining = false;
} else {
opts.messageCombining = true;
}
}
(function() {
var dataCookie = getCookie('connData');
if (dataCookie) {
var dataJ;
try {
dataJ = $.parseJSON(dataCookie);
} catch (e) {
window.onerror('JSON '+e+'. '+dataCookie, 'browserOutput.html', 434);
return;
}
opts.clientData = dataJ;
}
})();
/*****************************************
*
* BASE CHAT OUTPUT EVENTS
*
******************************************/
$('body').on('click', 'a', function(e) {
e.preventDefault();
});
$('body').on('mousedown', function(e) {
var $target = $(e.target);
if ($contextMenu && opts.hasOwnProperty('contextMenuTarget') && opts.contextMenuTarget) {
hideContextMenu();
return false;
}
if ($target.is('a') || $target.parent('a').length || $target.is('input') || $target.is('textarea')) {
opts.preventFocus = true;
} else {
opts.preventFocus = false;
opts.mouseDownX = e.pageX;
opts.mouseDownY = e.pageY;
}
});
$messages.on('mousedown', function(e) {
if ($selectedSub && $selectedSub.is(':visible')) {
$selectedSub.slideUp('fast', subSlideUp);
clearInterval(opts.selectedSubLoop);
}
});
$('body').on('mouseup', function(e) {
if (!opts.preventFocus &&
(e.pageX >= opts.mouseDownX - opts.clickTolerance && e.pageX <= opts.mouseDownX + opts.clickTolerance) &&
(e.pageY >= opts.mouseDownY - opts.clickTolerance && e.pageY <= opts.mouseDownY + opts.clickTolerance)
) {
opts.mouseDownX = null;
opts.mouseDownY = null;
runByond('byond://winset?mapwindow.map.focus=true');
}
});
$messages.on('click', 'a', function(e) {
var href = $(this).attr('href');
$(this).addClass('visited');
if (href[0] == '?' || (href.length >= 8 && href.substring(0,8) == 'byond://')) {
runByond(href);
} else {
href = escaper(href);
runByond('?action=openLink&link='+href);
}
runByond('byond://winset?mapwindow.map.focus=true');
});
$('body').on('keydown', function(e) {
if (e.target.nodeName == 'INPUT' || e.target.nodeName == 'TEXTAREA') {
return;
}
if (e.ctrlKey || e.altKey || e.shiftKey) { //Band-aid "fix" for allowing ctrl+c copy paste etc. Needs a proper fix.
return;
}
runByond('byond://winset?mapwindow.map.focus=true');
});
//Mildly hacky fix for scroll issues on mob change (interface gets resized sometimes, messing up snap-scroll)
$(window).on('resize', function(e) {
if ($(this).height() !== opts.priorChatHeight) {
$('body,html').scrollTop($messages.outerHeight());
opts.priorChatHeight = $(this).height();
}
});
/*****************************************
*
* OPTIONS INTERFACE EVENTS
*
******************************************/
$('body').on('click', '#newMessages', function(e) {
var messagesHeight = $messages.outerHeight();
$('body,html').scrollTop(messagesHeight);
$('#newMessages').remove();
runByond('byond://winset?mapwindow.map.focus=true');
});
$('#toggleOptions').click(function(e) {
handleToggleClick($subOptions, $(this));
});
$('#darkmodetoggle').click(function(e) {
swap();
});
$('#toggleAudio').click(function(e) {
handleToggleClick($subAudio, $(this));
});
$('.sub, .toggle').mouseenter(function() {
opts.suppressSubClose = true;
});
$('.sub, .toggle').mouseleave(function() {
opts.suppressSubClose = false;
});
$('#decreaseFont').click(function(e) {
savedConfig.fontsize = Math.max(parseInt(savedConfig.fontsize || 13) - 1, 1) + 'px';
$messages.css({'font-size': savedConfig.fontsize});
setCookie('fontsize', savedConfig.fontsize, 365);
internalOutput('<span class="internal boldnshit">Font size set to '+savedConfig.fontsize+'</span>', 'internal');
});
$('#increaseFont').click(function(e) {
savedConfig.fontsize = (parseInt(savedConfig.fontsize || 13) + 1) + 'px';
$messages.css({'font-size': savedConfig.fontsize});
setCookie('fontsize', savedConfig.fontsize, 365);
internalOutput('<span class="internal boldnshit">Font size set to '+savedConfig.fontsize+'</span>', 'internal');
});
$('#decreaseLineHeight').click(function(e) {
savedConfig.lineheight = Math.max(parseFloat(savedConfig.lineheight || 1.2) - 0.1, 0.1).toFixed(1);
$("body").css({'line-height': savedConfig.lineheight});
setCookie('lineheight', savedConfig.lineheight, 365);
internalOutput('<span class="internal boldnshit">Line height set to '+savedConfig.lineheight+'</span>', 'internal');
});
$('#increaseLineHeight').click(function(e) {
savedConfig.lineheight = (parseFloat(savedConfig.lineheight || 1.2) + 0.1).toFixed(1);
$("body").css({'line-height': savedConfig.lineheight});
setCookie('lineheight', savedConfig.lineheight, 365);
internalOutput('<span class="internal boldnshit">Line height set to '+savedConfig.lineheight+'</span>', 'internal');
});
$('#togglePing').click(function(e) {
if (opts.pingDisabled) {
$('#ping').slideDown('fast');
opts.pingDisabled = false;
} else {
$('#ping').slideUp('fast');
opts.pingDisabled = true;
}
setCookie('pingdisabled', (opts.pingDisabled ? 'true' : 'false'), 365);
});
$('#saveLog').click(function(e) {
// Requires IE 10+ to issue download commands. Just opening a popup
// window will cause Ctrl+S to save a blank page, ignoring innerHTML.
if (!window.Blob) {
output('<span class="big red">This function is only supported on IE 10 and up. Upgrade if possible.</span>', 'internal');
return;
}
$.ajax({
type: 'GET',
url: 'browserOutput_white.css',
success: function(styleData) {
var blob = new Blob(['<head><title>Chat Log</title><style>', styleData, '</style></head><body>', $messages.html(), '</body>']);
var fname = 'SS13 Chat Log';
var date = new Date(), month = date.getMonth(), day = date.getDay(), hours = date.getHours(), mins = date.getMinutes(), secs = date.getSeconds();
fname += ' ' + date.getFullYear() + '-' + (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day;
fname += ' ' + (hours < 10 ? '0' : '') + hours + (mins < 10 ? '0' : '') + mins + (secs < 10 ? '0' : '') + secs;
fname += '.html';
window.navigator.msSaveBlob(blob, fname);
}
});
});
$('#highlightTerm').click(function(e) {
if ($('.popup .highlightTerm').is(':visible')) {return;}
var termInputs = '';
for (var i = 0; i < opts.highlightLimit; i++) {
termInputs += '<div><input type="text" name="highlightTermInput'+i+'" id="highlightTermInput'+i+'" class="highlightTermInput'+i+'" maxlength="255" value="'+(opts.highlightTerms[i] ? opts.highlightTerms[i] : '')+'" /></div>';
}
var popupContent = '<div class="head">String Highlighting</div>' +
'<div class="highlightPopup" id="highlightPopup">' +
'<div>Choose up to '+opts.highlightLimit+' strings that will highlight the line when they appear in chat.</div>' +
'<form id="highlightTermForm">' +
termInputs +
'<div><input type="text" name="highlightColor" id="highlightColor" class="highlightColor" '+
'style="background-color: '+(opts.highlightColor ? opts.highlightColor : '#FFFF00')+'" value="'+(opts.highlightColor ? opts.highlightColor : '#FFFF00')+'" maxlength="7" /></div>' +
'<div><input type="submit" name="highlightTermSubmit" id="highlightTermSubmit" class="highlightTermSubmit" value="Save" /></div>' +
'</form>' +
'</div>';
createPopup(popupContent, 250);
});
$('body').on('keyup', '#highlightColor', function() {
var color = $('#highlightColor').val();
color = color.trim();
if (!color || color.charAt(0) != '#') return;
$('#highlightColor').css('background-color', color);
});
$('body').on('submit', '#highlightTermForm', function(e) {
e.preventDefault();
opts.highlightTerms = [];
for (var count = 0; count < opts.highlightLimit; count++) {
var term = $('#highlightTermInput'+count).val();
if (term !== null && /\S/.test(term)) {
opts.highlightTerms.push(term.trim().toLowerCase());
}
}
var color = $('#highlightColor').val();
color = color.trim();
if (color == '' || color.charAt(0) != '#') {
opts.highlightColor = '#FFFF00';
} else {
opts.highlightColor = color;
}
var $popup = $('#highlightPopup').closest('.popup');
$popup.remove();
setCookie('highlightterms', JSON.stringify(opts.highlightTerms), 365);
setCookie('highlightcolor', opts.highlightColor, 365);
});
$('#clearMessages').click(function() {
$messages.empty();
opts.messageCount = 0;
});
$('#musicVolumeSpan').hover(function() {
$('#musicVolumeText').addClass('hidden');
$('#musicVolume').removeClass('hidden');
}, function() {
$('#musicVolume').addClass('hidden');
$('#musicVolumeText').removeClass('hidden');
});
$('#musicVolume').change(function() {
var newVolume = $('#musicVolume').val();
newVolume = clamp(newVolume, 0, 100);
$('#adminMusic').prop('volume', newVolume / 100);
setCookie('musicVolume', newVolume, 365);
opts.updatedVolume = newVolume;
if(!opts.volumeUpdating) {
setTimeout(sendVolumeUpdate, opts.volumeUpdateDelay);
opts.volumeUpdating = true;
}
});
$('#toggleCombine').click(function(e) {
opts.messageCombining = !opts.messageCombining;
setCookie('messagecombining', (opts.messageCombining ? 'true' : 'false'), 365);
});
$('img.icon').error(iconError);
/*****************************************
*
* KICK EVERYTHING OFF
*
******************************************/
runByond('?_src_=chat&proc=doneLoading');
if ($('#loading').is(':visible')) {
$('#loading').remove();
}
$('#userBar').show();
opts.priorChatHeight = $(window).height();
});