Updated chat post-processor to handle embedded command examples (both ! and /), embedded usernames, and embedded channel names

This commit is contained in:
rainbow napkin 2024-12-21 20:42:31 -05:00
parent 42d306e1f2
commit db5fac83ab
2 changed files with 147 additions and 6 deletions

View file

@ -125,7 +125,7 @@ class chatBox{
chatEntry.appendChild(chatBody); chatEntry.appendChild(chatBody);
this.chatBuffer.appendChild(this.chatPostprocessor.preprocess(chatEntry, data)); this.chatBuffer.appendChild(this.chatPostprocessor.postprocess(chatEntry, data));
//Set size to aspect on launch //Set size to aspect on launch
this.resizeAspect(); this.resizeAspect();

View file

@ -3,7 +3,7 @@ class chatPostprocessor{
this.client = client; this.client = client;
} }
preprocess(chatEntry, rawData){ postprocess(chatEntry, rawData){
this.rawData = rawData; this.rawData = rawData;
//Set current chat nodes //Set current chat nodes
this.chatEntry = chatEntry; this.chatEntry = chatEntry;
@ -15,6 +15,15 @@ class chatPostprocessor{
//Inject links into un-processed placeholders //Inject links into un-processed placeholders
this.processLinks(); this.processLinks();
//Inject clickable command examples
this.processCommandExamples();
//Inject clickable channel names
this.processChannelNames();
//Inject clickable usernames
this.processUsernames();
//Inject whitespace into un-processed words //Inject whitespace into un-processed words
this.addWhitespace(); this.addWhitespace();
@ -30,14 +39,17 @@ class chatPostprocessor{
splitMessage(){ splitMessage(){
//Create an empty array to hold the body //Create an empty array to hold the body
this.messageArray = []; this.messageArray = [];
//First unescape forward-slashes to keep them from splitting, then..
//Split string by word-boundries, with negative lookaheads to exclude file seperators so we don't split link placeholders //Split string by word-boundries, with negative lookaheads to exclude file seperators so we don't split link placeholders
const splitString = this.rawData.msg.split(/(?<!␜)\b/g); const splitString = this.rawData.msg.replaceAll('&#x2F;','/').split(/(?<!␜)\b/g);
//for each word in the splitstring //for each word in the splitstring
splitString.forEach((string) => { splitString.forEach((string) => {
//create a word object //create a word object
const wordObj = { const wordObj = {
string: string, //re-escape slashes for safety
string: string.replaceAll('/','&#x2F;'),
type: "word" type: "word"
} }
@ -123,15 +135,62 @@ class chatPostprocessor{
} }
injectNode(wordObj, vid); injectNode(wordObj, vid);
}else if(wordObj.type == 'command'){
//Create link node
const link = document.createElement('a');
//Set class
link.classList.add('chat-link');
//Set href and inner text
link.href = "javascript:";
link.innerText = wordObj.command;
//Add chatbox functionality
link.addEventListener('click', () => {this.client.chatBox.commandPreprocessor.preprocess(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);
//Set href and inner text
link.href = "javascript:";
link.innerText = 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');
//Set href and inner text
link.href = `/c/${wordObj.chan}`;
link.innerText = 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 each item found in the injection array
injectionArray.forEach((item) => { injectionArray.forEach((item) => {
//if it's a string //if it's a string
if(typeof item == "string"){ if(typeof item == "string"){
//Add it to the chat's innerHTML (it should already be escaped by the server) //Create span (can't add to innerHTML without clobbering sibling DOM nodes)
this.chatBody.innerHTML += item; const text = document.createElement('span');
//Set text to innerHTML (can't just append as a text node since the message was escaped server-side)
text.innerHTML = item;
this.chatBody.appendChild(text);
//Otherwise it should be a DOM node //Otherwise it should be a DOM node
}else{ }else{
//Append the node to our chat body //Append the node to our chat body
@ -167,6 +226,88 @@ class chatPostprocessor{
} }
} }
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 with slashes unescaped
const unescaped = wordObj.string.replaceAll('&#x2F;','/')
const lastChar = unescaped[unescaped.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 = unescaped.slice(0,-1);
const commandObj = {
type: "command",
string: nextWord.string,
command: command
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
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 unescaped
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 & (avoid escaped HTML char codes)
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,
chan: nextWord.string
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
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;
}
}
});
}
addWhitespace(){ addWhitespace(){
//for each word object in the body //for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => { this.messageArray.forEach((wordObj, wordIndex) => {