Added spoiler support.
This commit is contained in:
parent
b56c9a3365
commit
77bc549653
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@ node_modules/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
config.json
|
config.json
|
||||||
state.json
|
state.json
|
||||||
|
chatexamples.txt
|
||||||
|
|
@ -131,6 +131,17 @@ class commandProcessor{
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spoiler(preprocessor){
|
||||||
|
//splice out our command
|
||||||
|
preprocessor.commandArray.splice(0,2);
|
||||||
|
|
||||||
|
//Mark out the current message as a spoiler
|
||||||
|
preprocessor.chatType = 'spoiler';
|
||||||
|
|
||||||
|
//Make sure to throw the send flag
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
async announce(preprocessor){
|
async announce(preprocessor){
|
||||||
//Get the current channel from the database
|
//Get the current channel from the database
|
||||||
const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});
|
const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});
|
||||||
|
|
|
||||||
|
|
@ -283,8 +283,8 @@ span.user-entry{
|
||||||
#chat-panel-prompt-autocomplete{
|
#chat-panel-prompt-autocomplete{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -295,6 +295,8 @@ span.user-entry{
|
||||||
#chat-panel-prompt-autocomplete-filler{
|
#chat-panel-prompt-autocomplete-filler{
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
cursor: auto;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toke{
|
.toke{
|
||||||
|
|
|
||||||
|
|
@ -183,3 +183,19 @@ p.tooltip, h3.tooltip{
|
||||||
.context-menu button{
|
.context-menu button{
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spoiler:not(:hover){
|
||||||
|
color: black;
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
img{
|
||||||
|
color: black;
|
||||||
|
background-color: black;
|
||||||
|
filter: brightness(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactive, a{
|
||||||
|
color: black;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -159,9 +159,10 @@ class chatBox{
|
||||||
const match = this.checkAutocomplete();
|
const match = this.checkAutocomplete();
|
||||||
|
|
||||||
//Set placeholder to space out the autocomplete display
|
//Set placeholder to space out the autocomplete display
|
||||||
this.autocompletePlaceholder.innerHTML = this.chatPrompt.value;
|
//Use text content because it's unescaped, and while this only effects local users, it'll keep someone from noticing and whinging about it
|
||||||
|
this.autocompletePlaceholder.textContent = this.chatPrompt.value;
|
||||||
//Set the autocomplete display
|
//Set the autocomplete display
|
||||||
this.autocompleteDisplay.innerHTML = match.match.replace(match.word, '');
|
this.autocompleteDisplay.textContent = match.match.replace(match.word, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
tabComplete(event){
|
tabComplete(event){
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class chatPostprocessor{
|
||||||
//Set current chat nodes
|
//Set current chat nodes
|
||||||
this.chatEntry = chatEntry;
|
this.chatEntry = chatEntry;
|
||||||
this.chatBody = this.chatEntry.querySelector(".chat-entry-body");
|
this.chatBody = this.chatEntry.querySelector(".chat-entry-body");
|
||||||
|
this.filterSpans = [];
|
||||||
|
|
||||||
//Split the chat message into an array of objects representing each word
|
//Split the chat message into an array of objects representing each word
|
||||||
this.splitMessage();
|
this.splitMessage();
|
||||||
|
|
@ -45,6 +46,9 @@ class chatPostprocessor{
|
||||||
//Handle non-standard chat types
|
//Handle non-standard chat types
|
||||||
this.handleChatType();
|
this.handleChatType();
|
||||||
|
|
||||||
|
//Process spoilers
|
||||||
|
this.processSpoilers();
|
||||||
|
|
||||||
//Inject the pre-processed chat into the chatEntry node
|
//Inject the pre-processed chat into the chatEntry node
|
||||||
this.injectBody();
|
this.injectBody();
|
||||||
|
|
||||||
|
|
@ -56,16 +60,19 @@ class chatPostprocessor{
|
||||||
//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..
|
//First unescape char codes to keep from splitting on them
|
||||||
//Split string by word-boundries, with negative lookaheads to exclude file seperators so we don't split link placeholders
|
//This also means all text should be added to element via textContent and *NOT* innerHTML
|
||||||
const splitString = this.rawData.msg.replaceAll('/','/').split(/(?<!␜)\b/g);
|
//I'd rather not do this, but pre-processing everything while preserving codes is a fucking nightmare
|
||||||
|
//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
|
||||||
|
//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|(?<=\w)\b|(?=\s)\B|(?<=\s)\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 = {
|
||||||
//re-escape slashes for safety
|
string: string,
|
||||||
string: string.replaceAll('/','/'),
|
filterClasses: [],
|
||||||
type: "word"
|
type: "word"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,14 +83,18 @@ class chatPostprocessor{
|
||||||
|
|
||||||
injectBody(){
|
injectBody(){
|
||||||
//Create an empty array to hold the objects to inject
|
//Create an empty array to hold the objects to inject
|
||||||
const injectionArray = [""];
|
const injectionArray = [];
|
||||||
|
|
||||||
const _this = this;
|
|
||||||
//For each word object
|
//For each word object
|
||||||
this.messageArray.forEach((wordObj) => {
|
this.messageArray.forEach((wordObj) => {
|
||||||
if(wordObj.type == 'word'){
|
if(wordObj.type == 'word'){
|
||||||
//Inject current wordObj string into the chat body
|
//Create span node
|
||||||
injectString(wordObj.string);
|
const span = document.createElement('span');
|
||||||
|
//Set span text
|
||||||
|
span.textContent = wordObj.string;
|
||||||
|
|
||||||
|
//Inject node into array
|
||||||
|
injectionArray.push(span);
|
||||||
}else if(wordObj.type == 'link'){
|
}else if(wordObj.type == 'link'){
|
||||||
//Create a link node from our link
|
//Create a link node from our link
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
|
|
@ -93,7 +104,7 @@ class chatPostprocessor{
|
||||||
link.textContent = wordObj.link;
|
link.textContent = wordObj.link;
|
||||||
|
|
||||||
//Append node to chatBody
|
//Append node to chatBody
|
||||||
injectNode(wordObj, link);
|
combineNode(wordObj, link);
|
||||||
}else if(wordObj.type == 'deadLink'){
|
}else if(wordObj.type == 'deadLink'){
|
||||||
//Create a text span node from our link
|
//Create a text span node from our link
|
||||||
const badLink = document.createElement('a');
|
const badLink = document.createElement('a');
|
||||||
|
|
@ -103,7 +114,7 @@ class chatPostprocessor{
|
||||||
badLink.textContent = wordObj.link;
|
badLink.textContent = wordObj.link;
|
||||||
|
|
||||||
//Append node to chatBody
|
//Append node to chatBody
|
||||||
injectNode(wordObj, badLink);
|
combineNode(wordObj, badLink);
|
||||||
}else if(wordObj.type == 'malformedLink'){
|
}else if(wordObj.type == 'malformedLink'){
|
||||||
//Create a text span node from our link
|
//Create a text span node from our link
|
||||||
const malformedLink = document.createElement('span');
|
const malformedLink = document.createElement('span');
|
||||||
|
|
@ -113,7 +124,7 @@ class chatPostprocessor{
|
||||||
malformedLink.textContent = wordObj.link;
|
malformedLink.textContent = wordObj.link;
|
||||||
|
|
||||||
//Append node to chatBody
|
//Append node to chatBody
|
||||||
injectNode(wordObj, malformedLink);
|
combineNode(wordObj, malformedLink);
|
||||||
}else if(wordObj.type == 'image'){
|
}else if(wordObj.type == 'image'){
|
||||||
//Create an img node from our link
|
//Create an img node from our link
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
|
|
@ -130,7 +141,7 @@ class chatPostprocessor{
|
||||||
}
|
}
|
||||||
|
|
||||||
//Append node to chatBody
|
//Append node to chatBody
|
||||||
injectNode(wordObj, img);
|
combineNode(wordObj, img);
|
||||||
}else if(wordObj.type == 'video'){
|
}else if(wordObj.type == 'video'){
|
||||||
//Create a video node from our link
|
//Create a video node from our link
|
||||||
const vid = document.createElement('video');
|
const vid = document.createElement('video');
|
||||||
|
|
@ -150,7 +161,7 @@ class chatPostprocessor{
|
||||||
vid.title = `[${emote.name}]`;
|
vid.title = `[${emote.name}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
injectNode(wordObj, vid);
|
combineNode(wordObj, vid);
|
||||||
}else if(wordObj.type == 'command'){
|
}else if(wordObj.type == 'command'){
|
||||||
//Create link node
|
//Create link node
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
|
|
@ -158,7 +169,7 @@ class chatPostprocessor{
|
||||||
link.classList.add('chat-link');
|
link.classList.add('chat-link');
|
||||||
//Set href and inner text
|
//Set href and inner text
|
||||||
link.href = "javascript:";
|
link.href = "javascript:";
|
||||||
link.innerText = wordObj.command;
|
link.textContent = wordObj.command;
|
||||||
|
|
||||||
//Add chatbox functionality
|
//Add chatbox functionality
|
||||||
link.addEventListener('click', () => {this.client.chatBox.commandPreprocessor.preprocess(wordObj.command)});
|
link.addEventListener('click', () => {this.client.chatBox.commandPreprocessor.preprocess(wordObj.command)});
|
||||||
|
|
@ -172,7 +183,7 @@ class chatPostprocessor{
|
||||||
link.classList.add(wordObj.color);
|
link.classList.add(wordObj.color);
|
||||||
//Set href and inner text
|
//Set href and inner text
|
||||||
link.href = "javascript:";
|
link.href = "javascript:";
|
||||||
link.innerText = wordObj.string;
|
link.textContent = wordObj.string;
|
||||||
|
|
||||||
//add chatbox functionality
|
//add chatbox functionality
|
||||||
link.addEventListener('click', () => {this.client.chatBox.chatPrompt.value += `${wordObj.string} `});
|
link.addEventListener('click', () => {this.client.chatBox.chatPrompt.value += `${wordObj.string} `});
|
||||||
|
|
@ -186,7 +197,7 @@ class chatPostprocessor{
|
||||||
link.classList.add('chat-link');
|
link.classList.add('chat-link');
|
||||||
//Set href and inner text
|
//Set href and inner text
|
||||||
link.href = `/c/${wordObj.chan}`;
|
link.href = `/c/${wordObj.chan}`;
|
||||||
link.innerText = wordObj.string;
|
link.textContent = wordObj.string;
|
||||||
|
|
||||||
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
|
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
|
||||||
injectionArray.push(link);
|
injectionArray.push(link);
|
||||||
|
|
@ -198,47 +209,60 @@ class chatPostprocessor{
|
||||||
});
|
});
|
||||||
|
|
||||||
//For each item found in the injection array
|
//For each item found in the injection array
|
||||||
injectionArray.forEach((item) => {
|
for(let itemIndex in injectionArray){
|
||||||
//if it's a string
|
const item = injectionArray[itemIndex];
|
||||||
if(typeof item == "string"){
|
|
||||||
//Create span (can't add to innerHTML without clobbering sibling DOM nodes)
|
|
||||||
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);
|
//Currently this doesnt support multiple overlapping span-type filters
|
||||||
//Otherwise it should be a DOM node
|
//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{
|
}else{
|
||||||
//Append the node to our chat body
|
//Use the existing span
|
||||||
this.chatBody.appendChild(item);
|
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
|
//Like string.replace except it actually injects the node so we can keep things like event handlers
|
||||||
function injectNode(wordObj, node, placeholder = '␜'){
|
function combineNode(wordObj, node, placeholder = '␜'){
|
||||||
//Split string by the placeholder so we can keep surrounding whitespace
|
//Split string by the placeholder so we can keep surrounding whitespace
|
||||||
const splitWord = wordObj.string.split(placeholder, 2);
|
const splitWord = wordObj.string.split(placeholder, 2);
|
||||||
|
|
||||||
//Append the first half of the string
|
//Create combined node
|
||||||
injectString(splitWord[0]);
|
const combinedSpan = document.createElement('span');
|
||||||
|
|
||||||
//Append the node
|
//Add the first part of the text
|
||||||
injectionArray.push(node);
|
combinedSpan.textContent = splitWord[0];
|
||||||
|
|
||||||
//Append the second half of the string
|
//Add in the requestd node
|
||||||
injectString(splitWord[1]);
|
combinedSpan.appendChild(node);
|
||||||
}
|
|
||||||
|
|
||||||
function injectString(string){
|
//Finish it off with the last bit of text
|
||||||
//If the last item was a string
|
combinedSpan.insertAdjacentText('beforeend', splitWord[1]);
|
||||||
if(typeof injectionArray[injectionArray.length - 1] == "string"){
|
|
||||||
//add the word string on to the end of the string
|
//Add to injection array as three nested items to keep arrays lined up
|
||||||
injectionArray[injectionArray.length - 1] += string;
|
injectionArray.push(combinedSpan);
|
||||||
}else{
|
|
||||||
//Pop the string at the end of the array
|
|
||||||
injectionArray.push(string);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,9 +271,8 @@ class chatPostprocessor{
|
||||||
this.messageArray.forEach((wordObj, wordIndex) => {
|
this.messageArray.forEach((wordObj, wordIndex) => {
|
||||||
//if the word object hasn't been pre-processed elsewhere
|
//if the word object hasn't been pre-processed elsewhere
|
||||||
if(wordObj.type == "word"){
|
if(wordObj.type == "word"){
|
||||||
//Get last char of current word with slashes unescaped
|
//Get last char of current word
|
||||||
const unescaped = wordObj.string.replaceAll('/','/')
|
const lastChar = wordObj.string[wordObj.string.length - 1];
|
||||||
const lastChar = unescaped[unescaped.length - 1];
|
|
||||||
|
|
||||||
//if the last char is !
|
//if the last char is !
|
||||||
if(lastChar == '!' || lastChar == '/'){
|
if(lastChar == '!' || lastChar == '/'){
|
||||||
|
|
@ -259,11 +282,12 @@ class chatPostprocessor{
|
||||||
if(nextWord != null){
|
if(nextWord != null){
|
||||||
const command = lastChar + nextWord.string;
|
const command = lastChar + nextWord.string;
|
||||||
//Take out the command marker
|
//Take out the command marker
|
||||||
this.messageArray[wordIndex].string = unescaped.slice(0,-1);
|
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
|
||||||
|
|
||||||
const commandObj = {
|
const commandObj = {
|
||||||
type: "command",
|
type: "command",
|
||||||
string: nextWord.string,
|
string: nextWord.string,
|
||||||
|
filterClasses: [],
|
||||||
command: command
|
command: command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,12 +303,12 @@ class chatPostprocessor{
|
||||||
this.messageArray.forEach((wordObj, wordIndex) => {
|
this.messageArray.forEach((wordObj, wordIndex) => {
|
||||||
//if the word object hasn't been pre-processed elsewhere
|
//if the word object hasn't been pre-processed elsewhere
|
||||||
if(wordObj.type == "word"){
|
if(wordObj.type == "word"){
|
||||||
//Get last char of current word with slashes unescaped
|
//Get last char of current word with slashes pounds
|
||||||
const lastChar = wordObj.string[wordObj.string.length - 1];
|
const lastChar = wordObj.string[wordObj.string.length - 1];
|
||||||
const secondLastChar = wordObj.string[wordObj.string.length - 2];
|
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 the last char is # and the second to last char isn't & or # (avoid spoilers)
|
||||||
if(lastChar == '#' && secondLastChar != '&'){
|
if(lastChar == '#' && secondLastChar != '#'){
|
||||||
//get next word
|
//get next word
|
||||||
const nextWord = this.messageArray[wordIndex + 1];
|
const nextWord = this.messageArray[wordIndex + 1];
|
||||||
//if we have another word
|
//if we have another word
|
||||||
|
|
@ -295,6 +319,7 @@ class chatPostprocessor{
|
||||||
const commandObj = {
|
const commandObj = {
|
||||||
type: "channel",
|
type: "channel",
|
||||||
string: lastChar + nextWord.string,
|
string: lastChar + nextWord.string,
|
||||||
|
filterClasses: [],
|
||||||
chan: nextWord.string
|
chan: nextWord.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,6 +374,50 @@ class chatPostprocessor{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processSpoilers(){
|
||||||
|
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
|
||||||
|
const foundSpoilers = [];
|
||||||
|
//Spoiler detection stage
|
||||||
|
//For each word object in the message array
|
||||||
|
main: for(let wordIndex in this.messageArray){
|
||||||
|
//Get the current word object
|
||||||
|
const wordObj = this.messageArray[wordIndex];
|
||||||
|
|
||||||
|
//If its a regular word and contains '##'
|
||||||
|
if(wordObj.type == 'word' && wordObj.string.match('##')){
|
||||||
|
|
||||||
|
//Crawl through detected spoilers
|
||||||
|
for(let spoiler of foundSpoilers){
|
||||||
|
//If the current word object is part of a detected spoiler
|
||||||
|
if(wordIndex == spoiler.delimiters[0] || wordIndex == spoiler.delimiters[1]){
|
||||||
|
//ignore it and continue on to the next word object
|
||||||
|
continue main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Crawl throw word objects after the current one, not sure why wordIndex is saved as a string.. Thanks JS
|
||||||
|
for(let endIndex = (Number(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('##')){
|
||||||
|
//Scrape out delimiters
|
||||||
|
wordObj.string = wordObj.string.replaceAll("##",'');
|
||||||
|
endObj.string = endObj.string.replaceAll("##",'');
|
||||||
|
//Add it to the list of detected spoilers, skipping out the delimiters
|
||||||
|
foundSpoilers.push({class: "spoiler", index: [Number(wordIndex) + 1, endIndex - 1], delimiters: [Number(wordIndex), endIndex]});
|
||||||
|
//Break the nested end-detection loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add found spoilers to filters list
|
||||||
|
this.filterSpans = this.filterSpans.concat(foundSpoilers);
|
||||||
|
}
|
||||||
|
|
||||||
processLinks(){
|
processLinks(){
|
||||||
//If we don't have links
|
//If we don't have links
|
||||||
if(this.rawData.links == null){
|
if(this.rawData.links == null){
|
||||||
|
|
@ -385,7 +454,7 @@ class chatPostprocessor{
|
||||||
|
|
||||||
//Get the username and make it into an announcement title (little hacky but this *IS* postprocessing)
|
//Get the username and make it into an announcement title (little hacky but this *IS* postprocessing)
|
||||||
const userNode = this.chatEntry.querySelector('.chat-entry-username');
|
const userNode = this.chatEntry.querySelector('.chat-entry-username');
|
||||||
userNode.innerHTML = `${userNode.innerHTML.slice(0,-2)} Announcement`;
|
userNode.textContent = `${userNode.textContent.slice(0,-2)} Announcement`;
|
||||||
|
|
||||||
//Add/remove relevant classes
|
//Add/remove relevant classes
|
||||||
userNode.classList.remove('chat-entry-username');
|
userNode.classList.remove('chat-entry-username');
|
||||||
|
|
@ -410,6 +479,9 @@ class chatPostprocessor{
|
||||||
|
|
||||||
//Add toke/tokewhisper class
|
//Add toke/tokewhisper class
|
||||||
this.chatBody.classList.add("tokewhisper","serverwhisper");
|
this.chatBody.classList.add("tokewhisper","serverwhisper");
|
||||||
|
}else if(this.rawData.type == "spoiler"){
|
||||||
|
//Set whole-body spoiler
|
||||||
|
this.chatBody.classList.add("spoiler");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,8 +78,8 @@ class commandPreprocessor{
|
||||||
Object.keys(this.emotes).forEach((key) => {
|
Object.keys(this.emotes).forEach((key) => {
|
||||||
//For each emote in the current list
|
//For each emote in the current list
|
||||||
this.emotes[key].forEach((emote) => {
|
this.emotes[key].forEach((emote) => {
|
||||||
//Inject emote links into the message
|
//Inject emote links into the message, add invisible whitespace to the end to keep next character from mushing into the link
|
||||||
this.message = this.message.replaceAll(`[${emote.name}]`, emote.link);
|
this.message = this.message.replaceAll(`[${emote.name}]`, `${emote.link}ㅤ`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,21 +130,21 @@ class userList{
|
||||||
function renderContextMenu(event){
|
function renderContextMenu(event){
|
||||||
//Setup menu map
|
//Setup menu map
|
||||||
let menuMap = new Map([
|
let menuMap = new Map([
|
||||||
["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(client, `${user.user}`, `/panel/profile?user=${user.user}`))}],
|
["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, `${user.user}`, `/panel/profile?user=${user.user}`))}],
|
||||||
["Mention", ()=>{client.chatBox.catChat(`${user.user} `)}],
|
["Mention", ()=>{this.client.chatBox.catChat(`${user.user} `)}],
|
||||||
["Toke With", ()=>{client.chatBox.tokeWith(user.user)}],
|
["Toke With", ()=>{this.client.chatBox.tokeWith(user.user)}],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if(user.user != "Tokebot"){
|
if(user.user != "Tokebot" && user.user != this.client.user.user){
|
||||||
if(client.user.permMap.chan.get("kickUser")){
|
if(this.client.user.permMap.chan.get("kickUser")){
|
||||||
menuMap.set("Kick", ()=>{client.chatBox.commandPreprocessor.preprocess(`!kick ${user.user}`)});
|
menuMap.set("Kick", ()=>{this.client.chatBox.commandPreprocessor.preprocess(`!kick ${user.user}`)});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(client.user.permMap.chan.get("banUser")){
|
if(this.client.user.permMap.chan.get("banUser")){
|
||||||
menuMap.set("Channel Ban", ()=>{new chanBanUserPopup(client.channelName, user.user);});
|
menuMap.set("Channel Ban", ()=>{new chanBanUserPopup(this.client.channelName, user.user);});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(client.user.permMap.site.get("banUser")){
|
if(this.client.user.permMap.site.get("banUser")){
|
||||||
menuMap.set("Site Ban", ()=>{new banUserPopup(user.user);});
|
menuMap.set("Site Ban", ()=>{new banUserPopup(user.user);});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,14 @@ class canopyUtils{
|
||||||
this.ajax = new canopyAjaxUtils();
|
this.ajax = new canopyAjaxUtils();
|
||||||
this.ux = new canopyUXUtils();
|
this.ux = new canopyUXUtils();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//somehow this isn't built in to JS's unescape functions...
|
||||||
|
unescapeEntities(string){
|
||||||
|
//Create a new DOMParser and tell it to parse string as HTML
|
||||||
|
const outNode = new DOMParser().parseFromString(string, "text/html");
|
||||||
|
//Grab text content and send that shit out
|
||||||
|
return outNode.documentElement.textContent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class canopyUXUtils{
|
class canopyUXUtils{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue