676 lines
27 KiB
JavaScript
676 lines
27 KiB
JavaScript
/*Canopy - The next generation of stoner streaming software
|
||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU Affero General Public License as
|
||
published by the Free Software Foundation, either version 3 of the
|
||
License, or (at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU Affero General Public License for more details.
|
||
|
||
You should have received a copy of the GNU Affero General Public License
|
||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||
|
||
/**
|
||
* Class contianing client-side message post-processing code
|
||
*/
|
||
class chatPostprocessor{
|
||
/**
|
||
* Instantiates a new Chat Post-Processor object
|
||
* @param {channel} client - Parent client Management Object
|
||
*/
|
||
constructor(client){
|
||
/**
|
||
* Parent Client Management Object
|
||
*/
|
||
this.client = client;
|
||
}
|
||
|
||
/**
|
||
* Post-Processes a single message from the server and returns a presntable DOM Node
|
||
* @param {Node} chatEntry - Chat entry generated by initial chatBox method
|
||
* @param {Object} rawData - Raw data from server
|
||
* @returns {Node} Post-Processed Chat Entry
|
||
*/
|
||
postprocess(rawData, pm = false){
|
||
//Create empty array to hold filter spans
|
||
this.filterSpans = [];
|
||
//Set raw message data
|
||
this.rawData = rawData;
|
||
//Set current chat nodes
|
||
this.buildEntry(pm);
|
||
|
||
//Split the chat message into an array of objects representing each word/chunk
|
||
this.splitMessage();
|
||
|
||
//Process Qoutes
|
||
this.processQoute();
|
||
|
||
//Re-Hydrate and Inject links and embedded media into un-processed placeholders
|
||
this.processLinks();
|
||
|
||
//Inject clickable command examples
|
||
this.processCommandExamples();
|
||
|
||
//Inject clickable channel names
|
||
this.processChannelNames();
|
||
|
||
//Inject clickable usernames
|
||
this.processUsernames();
|
||
|
||
//Detect inline spoilers
|
||
this.processSpoilers();
|
||
|
||
//Detect inline strikethrough
|
||
this.processStrikethrough();
|
||
|
||
//Detect inline bold text
|
||
this.processBold();
|
||
|
||
//Detect inline italics
|
||
this.processItalics();
|
||
|
||
//Inject whitespace into long ass-words
|
||
this.addWhitespace();
|
||
|
||
//Handle non-standard chat types
|
||
this.handleChatType();
|
||
|
||
//Inject the pre-processed chat hyper-text into the chatEntry node
|
||
this.injectBody();
|
||
|
||
//Return the pre-processed node
|
||
return this.chatEntry;
|
||
}
|
||
|
||
buildEntry(pm){
|
||
const classSuffix = pm ? 'pm-panel-sesh' : 'chat';
|
||
const classSuffixAlt = pm ? classSuffix : 'chat-panel';
|
||
//Create chat-entry span
|
||
this.chatEntry = document.createElement('span');
|
||
this.chatEntry.classList.add(`${classSuffixAlt}-buffer`,`${classSuffix}-entry`,`${classSuffix}-entry-${this.rawData.user}`);
|
||
|
||
//Create high-level label
|
||
var highLevel = document.createElement('p');
|
||
highLevel.classList.add(`${classSuffixAlt}-buffer`,`${classSuffix}-entry-high-level`,"high-level");
|
||
highLevel.textContent = utils.unescapeEntities(`${this.rawData.highLevel}`);
|
||
this.chatEntry.appendChild(highLevel);
|
||
|
||
//If we're not using classic flair
|
||
if(this.rawData.flair != "classic"){
|
||
//Use flair
|
||
var flair = `flair-${this.rawData.flair}`;
|
||
//Otherwise
|
||
}else{
|
||
//Pull user's assigned color from the color map
|
||
var flair = this.client.userList.colorMap.get(this.rawData.user);
|
||
}
|
||
|
||
//Create username label
|
||
var userLabel = document.createElement('p');
|
||
userLabel.classList.add(`${classSuffixAlt}-buffer`, `${classSuffix}-entry-username`, );
|
||
|
||
//Create color span
|
||
var flairSpan = document.createElement('span');
|
||
flairSpan.classList.add(`${classSuffix}-entry-flair-span`, flair);
|
||
flairSpan.innerHTML = this.rawData.user;
|
||
|
||
//Inject flair span into user label before the colon
|
||
userLabel.innerHTML = `${flairSpan.outerHTML}: `;
|
||
|
||
//Append user label
|
||
this.chatEntry.appendChild(userLabel);
|
||
|
||
//Create chat body
|
||
this.chatBody = document.createElement('p');
|
||
this.chatBody.classList.add(`${classSuffixAlt}-buffer`,`${classSuffix}-entry-body`);
|
||
|
||
//Append chat body to chat entry
|
||
this.chatEntry.appendChild(this.chatBody);
|
||
}
|
||
|
||
/**
|
||
* Splits message into an array of Word Objects for further processing
|
||
*/
|
||
splitMessage(){
|
||
//Create an empty array to hold the body
|
||
this.messageArray = [];
|
||
|
||
//Unescape any sanatized char codes as we use .textContent for double-safety, and to prevent splitting of char codes
|
||
//Split string by word-boundries on words and non-word boundries around whitespace,
|
||
//with negative lookaheads to exclude file seperators so we don't split link placeholders, dashes so we dont split usernames and other things, and accented characters to keep those from splitting boundries too
|
||
//Also split by any invisble whitespace as a crutch to handle mushed links/emotes
|
||
//If we can one day figure out how to split non-repeating special chars instead of special chars with whitespace, that would be perf, unfortunately my brain hasn't rotted enough to understand regex like that just yet.
|
||
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?<!-)(?<!␜)(?=\w)\b|(?!-|[\u00C0-\u017F])(?<=\w)\b|(?=\s)\B|(?<=\s)\B|ㅤ/g);
|
||
|
||
//for each word in the splitstring
|
||
splitString.forEach((string) => {
|
||
//create a word object
|
||
const wordObj = {
|
||
string: string,
|
||
filterClasses: [],
|
||
type: "word"
|
||
}
|
||
|
||
//Add it to our body array
|
||
this.messageArray.push(wordObj);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Injects word objects into chat-entry as proper DOM Nodes
|
||
*/
|
||
injectBody(){
|
||
//Create an empty array to hold the objects to inject
|
||
const injectionArray = [];
|
||
|
||
//For each word object
|
||
this.messageArray.forEach((wordObj) => {
|
||
if(wordObj.type == 'word'){
|
||
//Create span node
|
||
const span = document.createElement('span');
|
||
|
||
//Set span filter classes
|
||
span.classList.add(...wordObj.filterClasses);
|
||
|
||
//Set span text
|
||
span.textContent = wordObj.string;
|
||
|
||
//Inject node into array
|
||
injectionArray.push(span);
|
||
}else if(wordObj.type == 'link'){
|
||
//Create a link node from our link
|
||
const link = document.createElement('a');
|
||
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||
link.href = wordObj.link;
|
||
link.target = "_blank";
|
||
//Use textContent to be safe since links can't be escaped serverside
|
||
link.textContent = wordObj.link;
|
||
|
||
//Append node to chatBody
|
||
combineNode(wordObj, link);
|
||
}else if(wordObj.type == 'deadLink'){
|
||
//Create a text span node from our link
|
||
const badLink = document.createElement('a');
|
||
badLink.classList.add('chat-dead-link', 'danger-link', ...wordObj.filterClasses);
|
||
badLink.href = wordObj.link;
|
||
badLink.target = "_blank";
|
||
//Use textContent to be safe since links can't be escaped serverside
|
||
badLink.textContent = wordObj.link;
|
||
|
||
//Append node to chatBody
|
||
combineNode(wordObj, badLink);
|
||
}else if(wordObj.type == 'malformedLink'){
|
||
//Create a text span node from our link
|
||
const malformedLink = document.createElement('span');
|
||
malformedLink.classList.add('chat-malformed-link', ...wordObj.filterClasses);
|
||
//Use textContent to be safe since links can't be escaped (this is why we don't just add it using injectString)
|
||
//arguably we could sanatize malformed links serverside since they're never actually used as links
|
||
malformedLink.textContent = wordObj.link;
|
||
|
||
//Append node to chatBody
|
||
combineNode(wordObj, malformedLink);
|
||
}else if(wordObj.type == 'image'){
|
||
//Create an img node from our link
|
||
const img = document.createElement('img');
|
||
img.classList.add('chat-img', ...wordObj.filterClasses);
|
||
img.src = wordObj.link;
|
||
|
||
//Look for an emote by link since emotes are tx'd as bare links
|
||
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
|
||
|
||
//If this is a known emote
|
||
if(emote != null){
|
||
//Set the hover text to the emote's name
|
||
img.title = `[${emote.name}]`;
|
||
}
|
||
|
||
//Append node to chatBody
|
||
combineNode(wordObj, img);
|
||
}else if(wordObj.type == 'video'){
|
||
//Create a video node from our link
|
||
const vid = document.createElement('video');
|
||
vid.classList.add('chat-video', ...wordObj.filterClasses);
|
||
vid.src = wordObj.link;
|
||
vid.controls = false;
|
||
vid.autoplay = true;
|
||
vid.loop = true;
|
||
vid.muted = true;
|
||
|
||
//Look for an emote by link since emotes are tx'd as bare links
|
||
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
|
||
|
||
//If this is a known emote
|
||
if(emote != null){
|
||
//Set the hover text to the emote's name
|
||
vid.title = `[${emote.name}]`;
|
||
}
|
||
|
||
combineNode(wordObj, vid);
|
||
}else if(wordObj.type == 'command'){
|
||
//Create link node
|
||
const link = document.createElement('a');
|
||
//Set class
|
||
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||
//Set href and inner text
|
||
link.href = "javascript:";
|
||
link.textContent = wordObj.command;
|
||
|
||
//Add chatbox functionality
|
||
link.addEventListener('click', () => {this.client.chatBox.transmit(wordObj.command)});
|
||
|
||
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
|
||
injectionArray.push(link);
|
||
}else if(wordObj.type == "username"){
|
||
//Create link node
|
||
const link = document.createElement('a');
|
||
//set class
|
||
link.classList.add(wordObj.color, ...wordObj.filterClasses);
|
||
//Set href and inner text
|
||
link.href = "javascript:";
|
||
link.textContent = wordObj.string;
|
||
|
||
//add chatbox functionality
|
||
link.addEventListener('click', () => {this.client.chatBox.chatPrompt.value += `${wordObj.string} `});
|
||
|
||
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
|
||
injectionArray.push(link);
|
||
}else if(wordObj.type == "channel"){
|
||
//Create link node
|
||
const link = document.createElement('a');
|
||
//set class
|
||
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||
//Set href and inner text
|
||
link.href = `/c/${wordObj.chan}`;
|
||
link.target = "_blank"
|
||
link.textContent = wordObj.string;
|
||
|
||
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
|
||
injectionArray.push(link);
|
||
}else{
|
||
console.warn("Unknown chat postprocessor word type:");
|
||
console.warn(wordObj);
|
||
}
|
||
|
||
});
|
||
|
||
//For each item found in the injection array
|
||
for(let itemIndex in injectionArray){
|
||
const item = injectionArray[itemIndex];
|
||
|
||
//Currently this doesnt support multiple overlapping span-type filters
|
||
//not a huge deal since we only have once (spoiler)
|
||
//All others can be applied per-node without any usability side effects
|
||
const curFilter = this.filterSpans.filter(filterFilters)[0];
|
||
let appendBody = this.chatBody;
|
||
|
||
//If we have a filter span
|
||
if(curFilter != null){
|
||
//If we're beggining the array
|
||
if(itemIndex == curFilter.index[0]){
|
||
//Create the span
|
||
appendBody = document.createElement('span');
|
||
//Label it for what it is
|
||
appendBody.classList.add(curFilter.class);
|
||
//Add it to the chat body
|
||
this.chatBody.appendChild(appendBody);
|
||
//Otherwise
|
||
}else{
|
||
//Use the existing span
|
||
appendBody = (this.chatBody.children[this.chatBody.children.length - 1]);
|
||
}
|
||
}
|
||
|
||
//Append the node to our chat body
|
||
appendBody.appendChild(item);
|
||
|
||
function filterFilters(filter){
|
||
//If the index is within the filter span
|
||
return filter.index[0] <= itemIndex && filter.index[1] >= itemIndex;
|
||
}
|
||
}
|
||
|
||
//Like string.replace except it actually injects the node so we can keep things like event handlers
|
||
function combineNode(wordObj, node, placeholder = '␜'){
|
||
//Split string by the placeholder so we can keep surrounding whitespace
|
||
const splitWord = wordObj.string.split(placeholder, 2);
|
||
|
||
//Create combined node
|
||
const combinedSpan = document.createElement('span');
|
||
|
||
//Add the first part of the text
|
||
combinedSpan.textContent = splitWord[0];
|
||
|
||
//Add in the requestd node
|
||
combinedSpan.appendChild(node);
|
||
|
||
//Finish it off with the last bit of text
|
||
combinedSpan.insertAdjacentText('beforeend', splitWord[1]);
|
||
|
||
//Add to injection array as three nested items to keep arrays lined up
|
||
injectionArray.push(combinedSpan);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Processes qouted text in chat
|
||
*/
|
||
processQoute(){
|
||
//If the message starts off with '>'
|
||
if(this.messageArray[0].string[0] == '>'){
|
||
this.chatBody.classList.add("qoute");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Processes clickable command examples in chat
|
||
*/
|
||
processCommandExamples(){
|
||
//for each word object in the body
|
||
this.messageArray.forEach((wordObj, wordIndex) => {
|
||
//if the word object hasn't been pre-processed elsewhere
|
||
if(wordObj.type == "word"){
|
||
//Get last char of current word
|
||
const lastChar = wordObj.string[wordObj.string.length - 1];
|
||
|
||
//if the last char is !
|
||
if(lastChar == '!' || lastChar == '/'){
|
||
//get next word
|
||
const nextWord = this.messageArray[wordIndex + 1];
|
||
//if we have another word
|
||
if(nextWord != null){
|
||
const command = lastChar + nextWord.string;
|
||
//Take out the command marker
|
||
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
|
||
|
||
const commandObj = {
|
||
type: "command",
|
||
string: nextWord.string,
|
||
filterClasses: [],
|
||
command: command
|
||
}
|
||
|
||
this.messageArray[wordIndex + 1] = commandObj;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Processes clickable channel names in chat
|
||
*/
|
||
processChannelNames(){
|
||
//for each word object in the body
|
||
this.messageArray.forEach((wordObj, wordIndex) => {
|
||
//if the word object hasn't been pre-processed elsewhere
|
||
if(wordObj.type == "word"){
|
||
//Get last char of current word with slashes pounds
|
||
const lastChar = wordObj.string[wordObj.string.length - 1];
|
||
const secondLastChar = wordObj.string[wordObj.string.length - 2];
|
||
|
||
//if the last char is # and the second to last char isn't & or # (avoid spoilers)
|
||
if(lastChar == '#' && secondLastChar != '#'){
|
||
//get next word
|
||
const nextWord = this.messageArray[wordIndex + 1];
|
||
//if we have another word
|
||
if(nextWord != null){
|
||
//Take out the chan marker
|
||
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
|
||
|
||
const commandObj = {
|
||
type: "channel",
|
||
string: lastChar + nextWord.string,
|
||
filterClasses: [],
|
||
chan: nextWord.string
|
||
}
|
||
|
||
this.messageArray[wordIndex + 1] = commandObj;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Processes clickable username callouts in chat
|
||
*/
|
||
processUsernames(){
|
||
//for each word object in the body
|
||
this.messageArray.forEach((wordObj, wordIndex) => {
|
||
//if the word object hasn't been pre-processed elsewhere
|
||
if(wordObj.type == "word"){
|
||
//Check for user and get their color
|
||
const color = this.client.userList.colorMap.get(wordObj.string);
|
||
|
||
//If the current word is the username of a connected user
|
||
if(color != null){
|
||
//Mark it as so
|
||
this.messageArray[wordIndex].type = "username";
|
||
//Store their color
|
||
this.messageArray[wordIndex].color = color;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Injects invisible whitespace in long-ass words to prevent fucking up the chat buffer size
|
||
*/
|
||
addWhitespace(){
|
||
//for each word object in the body
|
||
this.messageArray.forEach((wordObj, wordIndex) => {
|
||
//if the word object hasn't been pre-processed elsewhere
|
||
if(wordObj.type == "word"){
|
||
//Create an empty array to hold our word
|
||
var wordArray = [];
|
||
//For each character in the string of the current word object
|
||
this.messageArray[wordIndex].string.split("").forEach((char, charIndex) => {
|
||
//push the current character to the wordArray
|
||
wordArray.push(char);
|
||
//After eight characters
|
||
if(charIndex > 8){
|
||
//Push an invisible line-break character between every character
|
||
wordArray.push("ㅤ");
|
||
}
|
||
|
||
});
|
||
|
||
//Join the wordArray into a single string, and use it to set the current wordObject's string
|
||
this.messageArray[wordIndex].string = wordArray.join("");
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Searches for text in-between a specific delimiter and runs a given callback against it
|
||
*
|
||
* Internal command used by several text filters to prevent code re-writes
|
||
* @param {String} delimiter - delimiter to search string by
|
||
* @param {Function} cb - Callback function to run against found strings
|
||
* @returns {Array} - list of found instances of filter
|
||
*/
|
||
processFilter(delimiter, cb){
|
||
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
|
||
const foundFilters = [];
|
||
//Spoiler detection stage
|
||
//For each word object in the message array
|
||
main: for(let wordIndex = 0; wordIndex < this.messageArray.length; wordIndex++){
|
||
//Get the current word object
|
||
const wordObj = this.messageArray[wordIndex];
|
||
|
||
//If its a regular word and contains '##'
|
||
if(wordObj.type == 'word' && wordObj.string.match(utils.escapeRegex(delimiter))){
|
||
|
||
//Crawl through detected spoilers
|
||
for(let spoiler of foundFilters){
|
||
//If the current word object is part of a detected spoiler
|
||
if(wordIndex == spoiler[0] || wordIndex == spoiler[1]){
|
||
//ignore it and continue on to the next word object
|
||
continue main;
|
||
}
|
||
}
|
||
|
||
//Crawl throw word objects after the current one
|
||
for(let endIndex = (wordIndex + 1); endIndex < this.messageArray.length; endIndex++){
|
||
//Get the current end object
|
||
const endObj = this.messageArray[endIndex];
|
||
|
||
//If its a regular word and contains '##'
|
||
if(endObj.type == 'word' && endObj.string.match(utils.escapeRegex(delimiter))){
|
||
//Setup the found filter array
|
||
const foundFilter = [wordIndex, endIndex];
|
||
|
||
//Scrape out delimiters
|
||
wordObj.string = wordObj.string.replaceAll(delimiter,'');
|
||
endObj.string = endObj.string.replaceAll(delimiter,'');
|
||
|
||
//Add it to the list of detected filters
|
||
foundFilters.push(foundFilter);
|
||
|
||
//Run the filter callback
|
||
cb(foundFilter)
|
||
|
||
//Break the nested end-detection loop
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return foundFilters;
|
||
}
|
||
|
||
/**
|
||
* Processes in-line spoilers
|
||
*/
|
||
processSpoilers(){
|
||
//Process spoilers using '##' delimiter
|
||
this.processFilter('##', (foundSpoiler)=>{
|
||
//For each found spoiler add it to the list of found filter spans
|
||
this.filterSpans.push({class: "spoiler", index: [foundSpoiler[0] + 1, foundSpoiler[1] - 1], delimiters: [foundSpoiler[0], foundSpoiler[1]]});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Processes in-line Strike-through
|
||
*/
|
||
processStrikethrough(){
|
||
//Process strikethrough's using '~~' delimiter
|
||
this.processFilter('~~', (foundStrikethrough)=>{
|
||
for(let wordIndex = foundStrikethrough[0]; wordIndex < foundStrikethrough[1]; wordIndex++){
|
||
this.messageArray[wordIndex].filterClasses.push("strikethrough");
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* Processes in-line Bold/Strong text
|
||
*/
|
||
processBold(){
|
||
//Process strong text using '*' delimiter
|
||
this.processFilter('**', (foundStrikethrough)=>{
|
||
for(let wordIndex = foundStrikethrough[0]; wordIndex < foundStrikethrough[1]; wordIndex++){
|
||
this.messageArray[wordIndex].filterClasses.push("bold");
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* Processes in-line Italics
|
||
*/
|
||
processItalics(){
|
||
//Process italics using '__' delimiter
|
||
this.processFilter('*', (foundStrikethrough)=>{
|
||
for(let wordIndex = foundStrikethrough[0]; wordIndex < foundStrikethrough[1]; wordIndex++){
|
||
this.messageArray[wordIndex].filterClasses.push("italics");
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* Processes clickable links and embedded media
|
||
*/
|
||
processLinks(){
|
||
//If we don't have links
|
||
if(this.rawData.links == null){
|
||
//Don't bother
|
||
return;
|
||
}
|
||
|
||
//For every link received in this message
|
||
this.rawData.links.forEach((link, linkIndex) => {
|
||
//For every word obj in the message array
|
||
this.messageArray.forEach((wordObj, wordIndex) => {
|
||
//Check current wordobj for link (placeholder may contain whitespace with it)
|
||
if(wordObj.string.match(`␜${linkIndex}`)){
|
||
//Set current word object in the body array to the new link object
|
||
this.messageArray[wordIndex] = {
|
||
//Don't want to use a numbered placeholder to make this easier during body injection
|
||
//but we also don't want to clobber any surrounding whitespace
|
||
string: wordObj.string.replace(`␜${linkIndex}`, '␜'),
|
||
link: link.link,
|
||
type: link.type,
|
||
filterClasses: []
|
||
}
|
||
}
|
||
})
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Marks chat nodes in-case of non-standard chat types
|
||
*/
|
||
handleChatType(){
|
||
if(this.rawData.type == "whisper"){
|
||
//add whisper class
|
||
this.chatBody.classList.add('whisper');
|
||
}else if(this.rawData.type == "announcement"){
|
||
//Squash the high-level
|
||
this.chatEntry.querySelector('.high-level').remove();
|
||
|
||
//Get the username and make it into an announcement title (little hacky but this *IS* postprocessing)
|
||
const userNode = this.chatEntry.querySelector('.chat-entry-username');
|
||
userNode.textContent = `${userNode.textContent.slice(0,-2)} Announcement`;
|
||
|
||
//Add/remove relevant classes
|
||
userNode.classList.remove('chat-entry-username');
|
||
userNode.classList.add('announcement-title');
|
||
this.chatBody.classList.add('announcement-body');
|
||
this.chatEntry.classList.add('announcement');
|
||
}else if(this.rawData.type == "toke"){
|
||
//Squash the high-level
|
||
this.chatEntry.querySelector('.high-level').remove();
|
||
|
||
//remove the username
|
||
this.chatEntry.querySelector('.chat-entry-username').remove();
|
||
|
||
//Add toke/tokewhisper class
|
||
this.chatBody.classList.add("toke");
|
||
}else if(this.rawData.type == "tokewhisper"){
|
||
//Squash the high-level
|
||
this.chatEntry.querySelector('.high-level').remove();
|
||
|
||
//remove the username
|
||
this.chatEntry.querySelector('.chat-entry-username').remove();
|
||
|
||
//Add toke/tokewhisper class
|
||
this.chatBody.classList.add("tokewhisper","serverwhisper");
|
||
}else if(this.rawData.type == "spoiler"){
|
||
//Set whole-body spoiler
|
||
this.chatBody.classList.add("spoiler");
|
||
}else if(this.rawData.type == "strikethrough"){
|
||
//Set whole-body spoiler
|
||
this.chatBody.classList.add("strikethrough");
|
||
}else if(this.rawData.type == "bold"){
|
||
//Set whole-body spoiler
|
||
this.chatBody.classList.add("bold");
|
||
}else if(this.rawData.type == "italics"){
|
||
//Set whole-body spoiler
|
||
this.chatBody.classList.add("italics");
|
||
}
|
||
}
|
||
} |