From 45a3c47608a088d5c942beb84a5b6517a88eea65 Mon Sep 17 00:00:00 2001 From: Letter N <24603524+LetterN@users.noreply.github.com> Date: Wed, 8 Jul 2020 15:10:49 +0800 Subject: [PATCH] removes fun --- code/controllers/subsystem/chat.dm | 9 +- code/modules/goonchat/browserOutput.dm | 144 ++++-- .../browserassets/html/browserOutput.html | 8 +- .../browserassets/js/browserOutput.js | 485 ++++++++++-------- 4 files changed, 381 insertions(+), 265 deletions(-) diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm index 8d4de0c091..bbeb0683f0 100644 --- a/code/controllers/subsystem/chat.dm +++ b/code/controllers/subsystem/chat.dm @@ -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", "
") message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") - message += "
" - + if (trailing_newline) + message += "
" //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) diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm index 6d9e141309..cd50408941 100644 --- a/code/modules/goonchat/browserOutput.dm +++ b/code/modules/goonchat/browserOutput.dm @@ -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, "Failed to load fancy chat, reverting to old chat. Certain features won't work.") +/// 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", + "$1" + ) + + 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", "
") - message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") + message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") //EIGHT SPACES IN TOTAL!! + if(trailing_newline) + message += "
" 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 diff --git a/code/modules/goonchat/browserassets/html/browserOutput.html b/code/modules/goonchat/browserassets/html/browserOutput.html index 0acb127517..ce51cd8de8 100644 --- a/code/modules/goonchat/browserassets/html/browserOutput.html +++ b/code/modules/goonchat/browserassets/html/browserOutput.html @@ -38,10 +38,10 @@
- Decrease font size - - Increase font size + - Decrease line height - - Increase line height + + Decrease font size + Increase font size + Decrease line height + Increase line height Toggle ping display Highlight string Save chat log diff --git a/code/modules/goonchat/browserassets/js/browserOutput.js b/code/modules/goonchat/browserassets/js/browserOutput.js index 0d53b44ba8..ea8996bdfa 100644 --- a/code/modules/goonchat/browserassets/js/browserOutput.js +++ b/code/modules/goonchat/browserassets/js/browserOutput.js @@ -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 ''+match+''; + $(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 ''; +} + +// 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("<", "<").replace(new RegExp(opts.highlightTerms[i], 'gi'), addHighlightMarkup); - break; - } - if (window.console) - console.log(newWord) - } - newText += newWord || words[w].replace("<", "<"); - 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('1 new message '); + //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('1 new message '); + } + } + } 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 = $('', {'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('Loaded font size setting of: '+savedConfig.sfontSize+'', 'internal'); + if (savedConfig.fontsize) { + $messages.css('font-size', savedConfig.fontsize); + internalOutput('Loaded font size setting of: '+savedConfig.fontsize+'', 'internal'); } - if (savedConfig.slineHeight) { - $("body").css('line-height', savedConfig.slineHeight); - internalOutput('Loaded line height setting of: '+savedConfig.slineHeight+'', 'internal'); + if (savedConfig.lineheight) { + $("body").css('line-height', savedConfig.lineheight); + internalOutput('Loaded line height setting of: '+savedConfig.lineheight+'', 'internal'); + } + // if(savedConfig.sdarkmode == 'true'){ + // swap(); + // } + if (savedConfig.scolorPreset) { + opts.colorPreset = Number(savedConfig.scolorPreset); + updateColorPreset(); + internalOutput('Loaded color preset of: '+colorPresets[opts.colorPreset]+'', 'internal'); } if (savedConfig.spingDisabled) { if (savedConfig.spingDisabled == 'true') { @@ -634,15 +778,11 @@ $(function() { internalOutput('Loaded ping display of: '+(opts.pingDisabled ? 'hidden' : 'visible')+'', '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('Loaded highlight strings of: ' + actualTerms+'', 'internal'); opts.highlightTerms = savedTerms; } @@ -651,13 +791,6 @@ $(function() { opts.highlightColor = savedConfig.shighlightColor; internalOutput('Loaded highlight color of: '+savedConfig.shighlightColor+'', 'internal'); } - - if (savedConfig.scolorPreset) { - opts.colorPreset = Number(savedConfig.scolorPreset); - updateColorPreset(); - internalOutput('Loaded color preset of: '+colorPresets[opts.colorPreset]+'', '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('Font size set to '+fontSize+'', 'internal'); + savedConfig.fontsize = Math.max(parseInt(savedConfig.fontsize || 13) - 1, 1) + 'px'; + $messages.css({'font-size': savedConfig.fontsize}); + setCookie('fontsize', savedConfig.fontsize, 365); + internalOutput('Font size set to '+savedConfig.fontsize+'', '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('Font size set to '+fontSize+'', 'internal'); + savedConfig.fontsize = (parseInt(savedConfig.fontsize || 13) + 1) + 'px'; + $messages.css({'font-size': savedConfig.fontsize}); + setCookie('fontsize', savedConfig.fontsize, 365); + internalOutput('Font size set to '+savedConfig.fontsize+'', '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('Line height set to '+lineheightvar+'', '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('Line height set to '+savedConfig.lineheight+'', '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('Line height set to '+lineheightvar+'', 'internal'); + savedConfig.lineheight = (parseFloat(savedConfig.lineheight || 1.2) + 0.1).toFixed(1); + $("body").css({'line-height': savedConfig.lineheight}); + setCookie('lineheight', savedConfig.lineheight, 365); + internalOutput('Line height set to '+savedConfig.lineheight+'', '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('This function is only supported on IE 10+. Upgrade if possible.', 'internal'); + output('This function is only supported on IE 10 and up. Upgrade if possible.', 'internal'); return; } $.ajax({ type: 'GET', - url: 'browserOutput.css', + url: 'browserOutput_white.css', success: function(styleData) { var blob = new Blob(['Chat Log', $messages.html(), '']); @@ -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); - - - + + + /***************************************** *