removes fun

This commit is contained in:
Letter N
2020-07-08 15:10:49 +08:00
parent bfa54f7e23
commit 45a3c47608
4 changed files with 381 additions and 265 deletions

View File

@@ -4,6 +4,7 @@ SUBSYSTEM_DEF(chat)
wait = 1
priority = FIRE_PRIORITY_CHAT
init_order = INIT_ORDER_CHAT
var/list/payload = list()
@@ -17,7 +18,7 @@ SUBSYSTEM_DEF(chat)
return
/datum/controller/subsystem/chat/proc/queue(target, message, handle_whitespace = TRUE)
/datum/controller/subsystem/chat/proc/queue(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = TRUE)
if(!target || !message)
return
@@ -35,8 +36,8 @@ SUBSYSTEM_DEF(chat)
if(handle_whitespace)
message = replacetext(message, "\n", "<br>")
message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]")
message += "<br>"
if (trailing_newline)
message += "<br>"
//url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
//Do the double-encoding here to save nanoseconds
@@ -47,7 +48,7 @@ SUBSYSTEM_DEF(chat)
var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
if(!C)
return
continue
//Send it to the old style output window.
SEND_TEXT(C, original_message)

View File

@@ -2,18 +2,30 @@
For the main html chat area
*********************************/
//Precaching a bunch of shit
/// Should match the value set in the browser js
#define MAX_COOKIE_LENGTH 5
//Precaching a bunch of shit. Someone ship this out of here
GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of icons for the browser output
//On client, created on login
//lazy renaming to chat_output, instead renamed to old chatOutput
/**
* The chatOutput datum exists to handle the goonchat browser.
* On client, created on Client/New()
*/
/datum/chatOutput
var/client/owner //client ref
/// The client that owns us.
var/client/owner
/// How many times client data has been checked
var/total_checks = 0
var/last_check = 0
var/loaded = FALSE // Has the client loaded the browser output area?
var/list/messageQueue //If they haven't loaded chat, this is where messages will go until they do
var/cookieSent = FALSE // Has the client sent a cookie for analysis
var/broken = FALSE
/// When to next clear the client data checks counter
var/next_time_to_clear = 0
/// Has the client loaded the browser output area?
var/loaded = FALSE
/// If they haven't loaded chat, this is where messages will go until they do
var/list/messageQueue
var/cookieSent = FALSE // Has the client sent a cookie for analysis
var/broken = FALSE
var/list/connectionHistory //Contains the connection history passed from chat cookie
var/adminMusicVolume = 25 //This is for the Play Global Sound verb
@@ -22,13 +34,18 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
messageQueue = list()
connectionHistory = list()
/**
* start: Tries to load the chat browser
* Aborts if a problem is encountered.
* Async because this is called from Client/New.
*/
/datum/chatOutput/proc/start()
set waitfor = FALSE
//Check for existing chat
if(!owner)
return FALSE
if(!winexists(owner, "browseroutput")) // Oh goddamnit.
set waitfor = FALSE
broken = TRUE
message_admins("Couldn't start chat for [key_name_admin(owner)]!")
. = FALSE
@@ -43,6 +60,7 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
return TRUE
/// Loads goonchat and sends assets.
/datum/chatOutput/proc/load()
set waitfor = FALSE
if(!owner)
@@ -53,6 +71,7 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
owner << browse(file('code/modules/goonchat/browserassets/html/browserOutput.html'), "window=browseroutput")
/// Interprets input from the client. Will send data back if required.
/datum/chatOutput/Topic(href, list/href_list)
if(usr.client != owner)
return TRUE
@@ -83,20 +102,22 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
if("setMusicVolume")
data = setMusicVolume(arglist(params))
if("colorPresetPost") //User just swapped color presets in their goonchat preferences. Do we do anything else?
switch(href_list["preset"])
if("light")
owner.force_white_theme()
if("dark" || "normal")
owner.force_dark_theme()
// if("swaptodarkmode")
// swaptodarkmode()
// if("swaptolightmode")
// swaptolightmode()
if(data)
ehjax_send(data = data)
//Called on chat output done-loading by JS.
/// Called on chat output done-loading by JS.
/datum/chatOutput/proc/doneLoading()
if(loaded)
return
@@ -113,34 +134,75 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
messageQueue = null
sendClientData()
syncRegex()
//do not convert to to_chat()
SEND_TEXT(owner, "<span class=\"userdanger\">Failed to load fancy chat, reverting to old chat. Certain features won't work.</span>")
/// Hides the standard output and makes the browser visible.
/datum/chatOutput/proc/showChat()
winset(owner, "output", "is-visible=false")
winset(owner, "browseroutput", "is-disabled=false;is-visible=true")
/// Calls syncRegex on all currently owned chatOutput datums
/proc/syncChatRegexes()
for (var/user in GLOB.clients)
var/client/C = user
var/datum/chatOutput/Cchat = C.chatOutput
if (Cchat && !Cchat.broken && Cchat.loaded)
Cchat.syncRegex()
/// Used to dynamically add regexes to the browser output. Currently only used by the IC filter.
/datum/chatOutput/proc/syncRegex()
var/list/regexes = list()
if (config.ic_filter_regex)
regexes["show_filtered_ic_chat"] = list(
config.ic_filter_regex.name,
"ig",
"<span class='boldwarning'>$1</span>"
)
if (regexes.len)
ehjax_send(data = list("syncRegex" = regexes))
/// Sends json encoded data to the browser.
/datum/chatOutput/proc/ehjax_send(client/C = owner, window = "browseroutput", data)
if(islist(data))
data = json_encode(data)
C << output("[data]", "[window]:ehjaxCallback")
/datum/chatOutput/proc/sendMusic(music, pitch)
/**
* Sends music data to the browser. If enabled by the browser, it will start playing.
* Arguments:
* music must be a https adress.
* extra_data is a list. The keys "pitch", "start" and "end" are used.
** "pitch" determines the playback rate
** "start" determines the start time of the sound
** "end" determines when the musics stops playing
*/
/datum/chatOutput/proc/sendMusic(music, pitch, list/extra_data) //someone remove pitch
if(!findtext(music, GLOB.is_http_protocol))
return
var/list/music_data = list("adminMusic" = url_encode(url_encode(music)))
if(pitch)
music_data["musicRate"] = pitch
if(extra_data?.len)
music_data["musicRate"] = extra_data["pitch"] || pitch
music_data["musicSeek"] = extra_data["start"]
music_data["musicHalt"] = extra_data["end"]
ehjax_send(data = music_data)
/// Stops music playing throw the browser.
/datum/chatOutput/proc/stopMusic()
ehjax_send(data = "stopMusic")
/// Setter for adminMusicVolume. Sanitizes the value to between 0 and 100.
/datum/chatOutput/proc/setMusicVolume(volume = "")
if(volume)
adminMusicVolume = clamp(text2num(volume), 0, 100)
//Sends client connection details to the chat to handle and save
/// Sends client connection details to the chat to handle and save
/datum/chatOutput/proc/sendClientData()
//Get dem deets
var/list/deets = list("clientData" = list())
@@ -150,11 +212,11 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
var/data = json_encode(deets)
ehjax_send(data = data)
//Called by client, sent data to investigate (cookie history so far)
/// Called by client, sent data to investigate (cookie history so far)
/datum/chatOutput/proc/analyzeClientData(cookie = "")
//Spam check
if(world.time > last_check + (3 SECONDS))
last_check = world.time
if(world.time > next_time_to_clear)
next_time_to_clear = world.time + (3 SECONDS)
total_checks = 0
total_checks += 1
@@ -172,12 +234,13 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
if (connData && islist(connData) && connData.len > 0 && connData["connData"])
connectionHistory = connData["connData"] //lol fuck
var/list/found = new()
if(connectionHistory.len > 5)
if(connectionHistory.len > MAX_COOKIE_LENGTH)
message_admins("[key_name(src.owner)] was kicked for an invalid ban cookie)")
qdel(owner)
return
for(var/i in min(connectionHistory.len, 5) to 1 step -1)
for(var/i in connectionHistory.len to 1 step -1)
if(QDELETED(owner))
//he got cleaned up before we were done
return
@@ -191,45 +254,33 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
//Uh oh this fucker has a history of playing on a banned account!!
if (found.len > 0)
//TODO: add a new evasion ban for the CURRENT client details, using the matched row details
message_admins("[key_name(src.owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
log_admin_private("[key_name(owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
cookieSent = TRUE
//Called by js client every 60 seconds
/// Called by js client every 60 seconds
/datum/chatOutput/proc/ping()
return "pong"
//Called by js client on js error
/// Called by js client on js error
/datum/chatOutput/proc/debug(error)
log_world("\[[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]\] Client: [(src.owner.key ? src.owner.key : src.owner)] triggered JS error: [error]")
//Global chat procs
/proc/to_chat_immediate(target, message, handle_whitespace=TRUE)
/// Global chat proc. to_chat_immediate will circumvent SSchat and send data as soon as possible.
/proc/to_chat_immediate(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
if(!target || !message)
return
//Ok so I did my best but I accept that some calls to this will be for shit like sound and images
//It stands that we PROBABLY don't want to output those to the browser output so just handle them here
if (istype(target, /savefile))
CRASH("Invalid message! [message]")
if(!istext(message))
if (istype(message, /image) || istype(message, /sound))
CRASH("Invalid message! [message]")
return
if(target == world)
target = GLOB.clients
var/original_message = message
//Some macros remain in the string even after parsing and fuck up the eventual output
message = replacetext(message, "\improper", "")
message = replacetext(message, "\proper", "")
if(handle_whitespace)
message = replacetext(message, "\n", "<br>")
message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]")
message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") //EIGHT SPACES IN TOTAL!!
if(trailing_newline)
message += "<br>"
if(islist(target))
// Do the double-encoding outside the loop to save nanoseconds
@@ -272,14 +323,19 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico
// url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
C << output(url_encode(url_encode(message)), "browseroutput:output")
/proc/to_chat(target, message, handle_whitespace = TRUE)
/// Sends a text message to the target.
/proc/to_chat(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
to_chat_immediate(target, message, handle_whitespace)
to_chat_immediate(target, message, handle_whitespace, trailing_newline, confidential)
return
SSchat.queue(target, message, handle_whitespace)
SSchat.queue(target, message, handle_whitespace, trailing_newline, confidential)
/datum/chatOutput/proc/swaptolightmode() //Dark mode light mode stuff. Yell at KMC if this breaks! (See darkmode.dm for documentation)
/// Dark mode light mode stuff. Yell at KMC if this breaks! (See darkmode.dm for documentation)
/datum/chatOutput/proc/swaptolightmode()
owner.force_white_theme()
/// Light mode stuff. (See darkmode.dm for documentation)
/datum/chatOutput/proc/swaptodarkmode()
owner.force_dark_theme()
#undef MAX_COOKIE_LENGTH

View File

@@ -38,10 +38,10 @@
<a href="#" class="subCell toggle" id="toggleOptions" title="Options"><i class="fas fa-cog"></i></a>
</div>
<div class="sub" id="subOptions">
<a href="#" class="subCell decreaseFont topCell" id="decreaseFont"><span>Decrease font size</span> <i class="fas fa-font">-</i></a>
<a href="#" class="subCell increaseFont" id="increaseFont"><span>Increase font size</span> <i class="fas fa-font">+</i></a>
<a href="#" class="subCell decreaseLineHeight" id="decreaseLineHeight"><span>Decrease line height</span> <i class="fas fa-text-height">-</i></a>
<a href="#" class="subCell increaseLineHeight" id="increaseLineHeight"><span>Increase line height</span> <i class="fas fa-text-height">+</i></a>
<a href="#" class="subCell decreaseFont topCell" id="decreaseFont"><span>Decrease font size</span> <i class="fas fa-font"></i></a>
<a href="#" class="subCell increaseFont" id="increaseFont"><span>Increase font size</span> <i class="fas fa-font"></i></a>
<a href="#" class="subCell decreaseLineHeight" id="decreaseLineHeight"><span>Decrease line height</span> <i class="fas fa-text-height"></i></a>
<a href="#" class="subCell increaseLineHeight" id="increaseLineHeight"><span>Increase line height</span> <i class="fas fa-text-height"></i></a>
<a href="#" class="subCell togglePing" id="togglePing"><span>Toggle ping display</span> <i class="fas fa-circle"></i></a>
<a href="#" class="subCell highlightTerm" id="highlightTerm"><span>Highlight string</span> <i class="fas fa-tag"></i></a>
<a href="#" class="subCell saveLog" id="saveLog"><span>Save chat log</span> <i class="fas fa-save"></i></a>

View File

@@ -29,12 +29,13 @@ var opts = {
'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?
'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?
'colorPreset': 0, // index in the color presets list.
//'darkmode':false, //Are we using darkmode? If not WHY ARE YOU LIVING IN 2009??? <- /tg/ take on darktheme
//Options menu
'selectedSubLoop': null, //Contains the interval loop for closing the selected sub menu
@@ -65,14 +66,17 @@ var opts = {
'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 = {};
// Array of names for chat display color presets.
// Array of names for chat display color presets. CIT SPECIFIC.
// If not set to normal, a CSS file `browserOutput_${name}.css` will be added to the head.
var colorPresets = [
'normal',
@@ -84,12 +88,6 @@ function clamp(val, min, max) {
return Math.max(min, Math.min(val, max))
}
function outerHTML(el) {
var wrap = document.createElement('div');
wrap.appendChild(el.cloneNode(true));
return wrap.innerHTML;
}
//Polyfill for fucking date now because of course IE8 and below don't support it
if (!Date.now) {
Date.now = function now() {
@@ -103,6 +101,7 @@ if (typeof String.prototype.trim !== 'function') {
};
}
// CIT SPECIFIC.
function updateColorPreset() {
var el = $("#colorPresetLink")[0];
el.href = "browserOutput_"+colorPresets[opts.colorPreset]+".css";
@@ -172,7 +171,7 @@ function byondDecode(message) {
// 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 {
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);
@@ -185,57 +184,71 @@ function byondDecode(message) {
return message;
}
//Actually turns the highlight term match into appropriate html
function addHighlightMarkup(match) {
var extra = '';
if (opts.highlightColor) {
extra += ' style="background-color: '+opts.highlightColor+'"';
function replaceRegex() {
var selectedRegex = replaceRegexes[$(this).attr('replaceRegex')];
if (selectedRegex) {
var replacedText = $(this).html().replace(selectedRegex[0], selectedRegex[1]);
$(this).html(replacedText);
}
return '<span class="highlight"'+extra+'>'+match+'</span>';
$(this).removeAttr('replaceRegex');
}
//Highlights words based on user settings
// 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) {
if (el.children.length > 0) {
for(var h = 0; h < el.children.length; h++){
highlightTerms(el.children[h]);
}
}
var pattern = new RegExp("(" + opts.highlightTerms.join('|') + ")", 'gi');
var nodes = getTextNodes(el, pattern);
var hasTextNode = false;
for (var node = 0; node < el.childNodes.length; node++)
{
if (el.childNodes[node].nodeType === 3)
{
hasTextNode = true;
break;
}
}
if (hasTextNode) { //If element actually has text
var newText = '';
for (var c = 0; c < el.childNodes.length; c++) { //Each child element
if (el.childNodes[c].nodeType === 3) { //Is it text only?
var words = el.childNodes[c].data.split(' ');
for (var w = 0; w < words.length; w++) { //Each word in the text
var newWord = null;
for (var i = 0; i < opts.highlightTerms.length; i++) { //Each highlight term
if (opts.highlightTerms[i] && words[w].toLowerCase().indexOf(opts.highlightTerms[i].toLowerCase()) > -1) { //If a match is found
newWord = words[w].replace("<", "&lt;").replace(new RegExp(opts.highlightTerms[i], 'gi'), addHighlightMarkup);
break;
}
if (window.console)
console.log(newWord)
}
newText += newWord || words[w].replace("<", "&lt;");
newText += w >= words.length - 1 ? '' : ' ';
}
} else { //Every other type of element
newText += outerHTML(el.childNodes[c]);
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;
}
}
el.innerHTML = newText;
}
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) {
@@ -268,41 +281,96 @@ function output(message, flag) {
message = byondDecode(message).trim();
//Stuff we do along with appending a message
var atBottom = false;
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');
//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 {
$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>');
//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
//}
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
@@ -323,6 +391,7 @@ function output(message, flag) {
badge = $('<span/>', {'class': 'r', 'text': 2});
}
lastmessages.html(message);
lastmessages.find('[replaceRegex]').each(replaceRegex);
lastmessages.append(badge);
badge.animate({
"font-size": "0.9em"
@@ -340,6 +409,13 @@ function output(message, flag) {
//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);
@@ -360,11 +436,11 @@ function output(message, flag) {
//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);
highlightTerms($(entry));
}
}
if (atBottom) {
if (!filteredOut && atBottom) {
$('body,html').scrollTop($messages.outerHeight());
}
}
@@ -408,6 +484,20 @@ function toHex(n) {
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};
@@ -488,6 +578,7 @@ function ehjaxCallback(data) {
} 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);
@@ -497,9 +588,32 @@ function ehjaxCallback(data) {
} 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];
}
}
}
}
@@ -530,6 +644,27 @@ function sendVolumeUpdate() {
}
}
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', '');
@@ -608,23 +743,32 @@ $(function() {
*
******************************************/
var savedConfig = {
'sfontSize': getCookie('fontsize'),
'slineHeight': getCookie('lineheight'),
fontsize: getCookie('fontsize'), //no need for compatabiliy, cookie name is the same
lineheight: getCookie('lineheight'),
'spingDisabled': getCookie('pingdisabled'),
'shighlightTerms': getCookie('highlightterms'),
'shighlightColor': getCookie('highlightcolor'),
'smusicVolume': getCookie('musicVolume'),
'smessagecombining': getCookie('messagecombining'),
'sdarkmode': getCookie('darkmode'),
'scolorPreset': getCookie('colorpreset'),
};
if (savedConfig.sfontSize) {
$messages.css('font-size', savedConfig.sfontSize);
internalOutput('<span class="internal boldnshit">Loaded font size setting of: '+savedConfig.sfontSize+'</span>', 'internal');
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.slineHeight) {
$("body").css('line-height', savedConfig.slineHeight);
internalOutput('<span class="internal boldnshit">Loaded line height setting of: '+savedConfig.slineHeight+'</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.scolorPreset) {
opts.colorPreset = Number(savedConfig.scolorPreset);
updateColorPreset();
internalOutput('<span class="internal boldnshit">Loaded color preset of: '+colorPresets[opts.colorPreset]+'</span>', 'internal');
}
if (savedConfig.spingDisabled) {
if (savedConfig.spingDisabled == 'true') {
@@ -634,15 +778,11 @@ $(function() {
internalOutput('<span class="internal boldnshit">Loaded ping display of: '+(opts.pingDisabled ? 'hidden' : 'visible')+'</span>', 'internal');
}
if (savedConfig.shighlightTerms) {
var savedTerms = $.parseJSON(savedConfig.shighlightTerms);
var actualTerms = '';
for (var i = 0; i < savedTerms.length; i++) {
if (savedTerms[i]) {
actualTerms += savedTerms[i] + ', ';
}
}
var savedTerms = $.parseJSON(savedConfig.shighlightTerms).filter(function (entry) {
return entry !== null && /\S/.test(entry);
});
var actualTerms = savedTerms.length != 0 ? savedTerms.join(', ') : null;
if (actualTerms) {
actualTerms = actualTerms.substring(0, actualTerms.length - 2);
internalOutput('<span class="internal boldnshit">Loaded highlight strings of: ' + actualTerms+'</span>', 'internal');
opts.highlightTerms = savedTerms;
}
@@ -651,13 +791,6 @@ $(function() {
opts.highlightColor = savedConfig.shighlightColor;
internalOutput('<span class="internal boldnshit">Loaded highlight color of: '+savedConfig.shighlightColor+'</span>', 'internal');
}
if (savedConfig.scolorPreset) {
opts.colorPreset = Number(savedConfig.scolorPreset);
updateColorPreset();
internalOutput('<span class="internal boldnshit">Loaded color preset of: '+colorPresets[opts.colorPreset]+'</span>', 'internal');
}
if (savedConfig.smusicVolume) {
var newVolume = clamp(savedConfig.smusicVolume, 0, 100);
$('#adminMusic').prop('volume', newVolume / 100);
@@ -669,7 +802,7 @@ $(function() {
else{
$('#adminMusic').prop('volume', opts.defaultMusicVolume / 100);
}
if (savedConfig.smessagecombining) {
if (savedConfig.smessagecombining == 'false') {
opts.messageCombining = false;
@@ -746,76 +879,17 @@ $(function() {
href = escaper(href);
runByond('?action=openLink&link='+href);
}
runByond('byond://winset?mapwindow.map.focus=true');
});
//Fuck everything about this event. Will look into alternatives.
$('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;
}
e.preventDefault()
var k = e.which;
// Hardcoded because else there would be no feedback message.
if (k == 113) { // F2
runByond('byond://winset?screenshot=auto');
internalOutput('Screenshot taken', 'internal');
}
var c = "";
switch (k) {
case 8:
c = 'BACK';
case 9:
c = 'TAB';
case 13:
c = 'ENTER';
case 19:
c = 'PAUSE';
case 27:
c = 'ESCAPE';
case 33: // Page up
c = 'NORTHEAST';
case 34: // Page down
c = 'SOUTHEAST';
case 35: // End
c = 'SOUTHWEST';
case 36: // Home
c = 'NORTHWEST';
case 37:
c = 'WEST';
case 38:
c = 'NORTH';
case 39:
c = 'EAST';
case 40:
c = 'SOUTH';
case 45:
c = 'INSERT';
case 46:
c = 'DELETE';
case 93: // That weird thing to the right of alt gr.
c = 'APPS';
default:
c = String.fromCharCode(k);
}
if (c.length == 0) {
if (!e.shiftKey) {
c = c.toLowerCase();
}
runByond('byond://winset?mapwindow.map.focus=true;mainwindow.input.text='+c);
return false;
} else {
runByond('byond://winset?mapwindow.map.focus=true');
return false;
}
runByond('byond://winset?mapwindow.map.focus=true');
});
//Mildly hacky fix for scroll issues on mob change (interface gets resized sometimes, messing up snap-scroll)
@@ -843,6 +917,9 @@ $(function() {
$('#toggleOptions').click(function(e) {
handleToggleClick($subOptions, $(this));
});
// $('#darkmodetoggle').click(function(e) {
// swap();
// });
$('#toggleAudio').click(function(e) {
handleToggleClick($subAudio, $(this));
});
@@ -856,41 +933,31 @@ $(function() {
});
$('#decreaseFont').click(function(e) {
var fontSize = parseInt($messages.css('font-size'));
fontSize = fontSize - 1 + 'px';
$messages.css({'font-size': fontSize});
setCookie('fontsize', fontSize, 365);
internalOutput('<span class="internal boldnshit">Font size set to '+fontSize+'</span>', 'internal');
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) {
var fontSize = parseInt($messages.css('font-size'));
fontSize = fontSize + 1 + 'px';
$messages.css({'font-size': fontSize});
setCookie('fontsize', fontSize, 365);
internalOutput('<span class="internal boldnshit">Font size set to '+fontSize+'</span>', 'internal');
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) {
var Heightline = parseFloat($("body").css('line-height'));
var Sizefont = parseFloat($("body").css('font-size'));
var lineheightvar = Heightline / Sizefont
lineheightvar -= 0.1;
lineheightvar = lineheightvar.toFixed(1)
$("body").css({'line-height': lineheightvar});
setCookie('lineheight', lineheightvar, 365);
internalOutput('<span class="internal boldnshit">Line height set to '+lineheightvar+'</span>', 'internal');
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) {
var Heightline = parseFloat($("body").css('line-height'));
var Sizefont = parseFloat($("body").css('font-size'));
var lineheightvar = Heightline / Sizefont
lineheightvar += 0.1;
lineheightvar = lineheightvar.toFixed(1)
$("body").css({'line-height': lineheightvar});
setCookie('lineheight', lineheightvar, 365);
internalOutput('<span class="internal boldnshit">Line height set to '+lineheightvar+'</span>', 'internal');
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) {
@@ -908,13 +975,13 @@ $(function() {
// 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+. Upgrade if possible.</span>', 'internal');
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.css',
url: 'browserOutput_white.css',
success: function(styleData) {
var blob = new Blob(['<head><title>Chat Log</title><style>', styleData, '</style></head><body>', $messages.html(), '</body>']);
@@ -958,20 +1025,12 @@ $(function() {
$('body').on('submit', '#highlightTermForm', function(e) {
e.preventDefault();
var count = 0;
while (count < opts.highlightLimit) {
opts.highlightTerms = [];
for (var count = 0; count < opts.highlightLimit; count++) {
var term = $('#highlightTermInput'+count).val();
if (term) {
term = term.trim();
if (term === '') {
opts.highlightTerms[count] = null;
} else {
opts.highlightTerms[count] = term.toLowerCase();
}
} else {
opts.highlightTerms[count] = null;
if (term !== null && /\S/.test(term)) {
opts.highlightTerms.push(term.trim().toLowerCase());
}
count++;
}
var color = $('#highlightColor').val();
@@ -992,8 +1051,8 @@ $(function() {
$messages.empty();
opts.messageCount = 0;
});
$('#changeColorPreset').click(function() {
$('#changeColorPreset').click(function() { //CIT SPECIFIC
opts.colorPreset = (opts.colorPreset+1) % colorPresets.length;
updateColorPreset();
setCookie('colorpreset', opts.colorPreset, 365);
@@ -1026,9 +1085,9 @@ $(function() {
});
$('img.icon').error(iconError);
/*****************************************
*