Finished up with inline and full-body chat-filters.
This commit is contained in:
parent
77bc549653
commit
4c31dbde1e
|
|
@ -142,6 +142,39 @@ class commandProcessor{
|
|||
return true
|
||||
}
|
||||
|
||||
strikethrough(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
preprocessor.chatType = 'strikethrough';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
bold(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
preprocessor.chatType = 'bold';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
italics(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
preprocessor.chatType = 'italics';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
async announce(preprocessor){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});
|
||||
|
|
|
|||
|
|
@ -187,15 +187,41 @@ p.tooltip, h3.tooltip{
|
|||
.spoiler:not(:hover){
|
||||
color: black;
|
||||
background-color: black;
|
||||
filter: brightness(0);
|
||||
|
||||
img{
|
||||
.interactive, a, img, video{
|
||||
color: black;
|
||||
background-color: black;
|
||||
filter: brightness(0);
|
||||
}
|
||||
}
|
||||
|
||||
.interactive, a{
|
||||
color: black;
|
||||
background-color: black;
|
||||
}
|
||||
.strikethrough{
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.strikethrough img, .strikethrough video, img.strikethrough, video.strikethrough{
|
||||
/* Oh yeah? Well, I'll just make my own damn strikethrough! With blackjack, and hookers! */
|
||||
filter: url('/img/strikethrough.svg#strikethroughFilter');
|
||||
}
|
||||
|
||||
.bold{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bold img, .bold video, img.bold, video.bold{
|
||||
max-height: 14em;
|
||||
}
|
||||
|
||||
.italics{
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.italics img, .italics video, img.italics, video.italics{
|
||||
transform: skew(-6deg);
|
||||
transform-origin: 50% 100%;
|
||||
}
|
||||
|
||||
.qoute{
|
||||
font-family: monospace;
|
||||
}
|
||||
14
www/img/strikethrough.svg
Normal file
14
www/img/strikethrough.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<filter id="strikethroughFilter">
|
||||
<feFlood
|
||||
result="floodFill"
|
||||
x="0"
|
||||
y="50%"
|
||||
width="100%"
|
||||
height="1"
|
||||
flood-color="black"
|
||||
flood-opacity="1"
|
||||
/>
|
||||
<feBlend in="floodFill" in2="SourceGraphic" mode="normal" />
|
||||
</filter>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 376 B |
|
|
@ -19,16 +19,20 @@ class chatPostprocessor{
|
|||
}
|
||||
|
||||
postprocess(chatEntry, rawData){
|
||||
//Create empty array to hold filter spans
|
||||
this.filterSpans = [];
|
||||
//Set raw message data
|
||||
this.rawData = rawData;
|
||||
//Set current chat nodes
|
||||
this.chatEntry = chatEntry;
|
||||
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/chunk
|
||||
this.splitMessage();
|
||||
|
||||
//Inject links into un-processed placeholders
|
||||
this.processQoute();
|
||||
|
||||
//Re-Hydrate and Inject links and embedded media into un-processed placeholders
|
||||
this.processLinks();
|
||||
|
||||
//Inject clickable command examples
|
||||
|
|
@ -40,15 +44,24 @@ class chatPostprocessor{
|
|||
//Inject clickable usernames
|
||||
this.processUsernames();
|
||||
|
||||
//Inject whitespace into un-processed words
|
||||
//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();
|
||||
|
||||
//Process spoilers
|
||||
this.processSpoilers();
|
||||
|
||||
|
||||
//Inject the pre-processed chat into the chatEntry node
|
||||
this.injectBody();
|
||||
|
||||
|
|
@ -64,8 +77,9 @@ class chatPostprocessor{
|
|||
//This also means all text should be added to element via textContent and *NOT* innerHTML
|
||||
//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
|
||||
//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|(?<=\w)\b|(?=\s)\B|(?<=\s)\B/g);
|
||||
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?<!␜)(?=\w)\b|(?<=\w)\b|(?=\s)\B|(?<=\s)\B|ㅤ/g);
|
||||
|
||||
//for each word in the splitstring
|
||||
splitString.forEach((string) => {
|
||||
|
|
@ -90,6 +104,10 @@ class chatPostprocessor{
|
|||
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;
|
||||
|
||||
|
|
@ -98,7 +116,7 @@ class chatPostprocessor{
|
|||
}else if(wordObj.type == 'link'){
|
||||
//Create a link node from our link
|
||||
const link = document.createElement('a');
|
||||
link.classList.add('chat-link');
|
||||
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||||
link.href = wordObj.link;
|
||||
//Use textContent to be safe since links can't be escaped serverside
|
||||
link.textContent = wordObj.link;
|
||||
|
|
@ -108,7 +126,7 @@ class chatPostprocessor{
|
|||
}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');
|
||||
badLink.classList.add('chat-dead-link', 'danger-link', ...wordObj.filterClasses);
|
||||
badLink.href = wordObj.link;
|
||||
//Use textContent to be safe since links can't be escaped serverside
|
||||
badLink.textContent = wordObj.link;
|
||||
|
|
@ -118,7 +136,7 @@ class chatPostprocessor{
|
|||
}else if(wordObj.type == 'malformedLink'){
|
||||
//Create a text span node from our link
|
||||
const malformedLink = document.createElement('span');
|
||||
malformedLink.classList.add('chat-malformed-link');
|
||||
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;
|
||||
|
|
@ -128,7 +146,7 @@ class chatPostprocessor{
|
|||
}else if(wordObj.type == 'image'){
|
||||
//Create an img node from our link
|
||||
const img = document.createElement('img');
|
||||
img.classList.add('chat-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
|
||||
|
|
@ -145,7 +163,7 @@ class chatPostprocessor{
|
|||
}else if(wordObj.type == 'video'){
|
||||
//Create a video node from our link
|
||||
const vid = document.createElement('video');
|
||||
vid.classList.add('chat-video');
|
||||
vid.classList.add('chat-video', ...wordObj.filterClasses);
|
||||
vid.src = wordObj.link;
|
||||
vid.controls = false;
|
||||
vid.autoplay = true;
|
||||
|
|
@ -166,7 +184,7 @@ class chatPostprocessor{
|
|||
//Create link node
|
||||
const link = document.createElement('a');
|
||||
//Set class
|
||||
link.classList.add('chat-link');
|
||||
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||||
//Set href and inner text
|
||||
link.href = "javascript:";
|
||||
link.textContent = wordObj.command;
|
||||
|
|
@ -180,7 +198,7 @@ class chatPostprocessor{
|
|||
//Create link node
|
||||
const link = document.createElement('a');
|
||||
//set class
|
||||
link.classList.add(wordObj.color);
|
||||
link.classList.add(wordObj.color, ...wordObj.filterClasses);
|
||||
//Set href and inner text
|
||||
link.href = "javascript:";
|
||||
link.textContent = wordObj.string;
|
||||
|
|
@ -194,7 +212,7 @@ class chatPostprocessor{
|
|||
//Create link node
|
||||
const link = document.createElement('a');
|
||||
//set class
|
||||
link.classList.add('chat-link');
|
||||
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||||
//Set href and inner text
|
||||
link.href = `/c/${wordObj.chan}`;
|
||||
link.textContent = wordObj.string;
|
||||
|
|
@ -266,6 +284,13 @@ class chatPostprocessor{
|
|||
}
|
||||
}
|
||||
|
||||
processQoute(){
|
||||
//If the message starts off with '>'
|
||||
if(this.messageArray[0].string[0] == '>'){
|
||||
this.chatBody.classList.add("qoute");
|
||||
}
|
||||
}
|
||||
|
||||
processCommandExamples(){
|
||||
//for each word object in the body
|
||||
this.messageArray.forEach((wordObj, wordIndex) => {
|
||||
|
|
@ -374,39 +399,47 @@ class chatPostprocessor{
|
|||
});
|
||||
}
|
||||
|
||||
processSpoilers(){
|
||||
processFilter(delimiter, cb){
|
||||
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
|
||||
const foundSpoilers = [];
|
||||
const foundFilters = [];
|
||||
//Spoiler detection stage
|
||||
//For each word object in the message array
|
||||
main: for(let wordIndex in this.messageArray){
|
||||
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('##')){
|
||||
if(wordObj.type == 'word' && wordObj.string.match(utils.escapeRegex(delimiter))){
|
||||
|
||||
//Crawl through detected spoilers
|
||||
for(let spoiler of foundSpoilers){
|
||||
for(let spoiler of foundFilters){
|
||||
//If the current word object is part of a detected spoiler
|
||||
if(wordIndex == spoiler.delimiters[0] || wordIndex == spoiler.delimiters[1]){
|
||||
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, not sure why wordIndex is saved as a string.. Thanks JS
|
||||
for(let endIndex = (Number(wordIndex) + 1); endIndex < this.messageArray.length; endIndex++){
|
||||
//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('##')){
|
||||
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("##",'');
|
||||
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]});
|
||||
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;
|
||||
}
|
||||
|
|
@ -414,8 +447,42 @@ class chatPostprocessor{
|
|||
}
|
||||
}
|
||||
|
||||
//Add found spoilers to filters list
|
||||
this.filterSpans = this.filterSpans.concat(foundSpoilers);
|
||||
return foundFilters;
|
||||
}
|
||||
|
||||
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]]});
|
||||
});
|
||||
}
|
||||
|
||||
processStrikethrough(){
|
||||
//Process strikethrough's using '~~' delimiter
|
||||
this.processFilter('~~', (foundStrikethrough)=>{
|
||||
for(let wordIndex = foundStrikethrough[0]; wordIndex < foundStrikethrough[1]; wordIndex++){
|
||||
this.messageArray[wordIndex].filterClasses.push("strikethrough");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
processBold(){
|
||||
//Process strong text using '*' delimiter
|
||||
this.processFilter('**', (foundStrikethrough)=>{
|
||||
for(let wordIndex = foundStrikethrough[0]; wordIndex < foundStrikethrough[1]; wordIndex++){
|
||||
this.messageArray[wordIndex].filterClasses.push("bold");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
processItalics(){
|
||||
//Process italics using '__' delimiter
|
||||
this.processFilter('*', (foundStrikethrough)=>{
|
||||
for(let wordIndex = foundStrikethrough[0]; wordIndex < foundStrikethrough[1]; wordIndex++){
|
||||
this.messageArray[wordIndex].filterClasses.push("italics");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
processLinks(){
|
||||
|
|
@ -437,7 +504,8 @@ class chatPostprocessor{
|
|||
//but we also don't want to clobber any surrounding whitespace
|
||||
string: wordObj.string.replace(`␜${linkIndex}`, '␜'),
|
||||
link: link.link,
|
||||
type: link.type
|
||||
type: link.type,
|
||||
filterClasses: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -482,6 +550,15 @@ class chatPostprocessor{
|
|||
}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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,8 +78,8 @@ class commandPreprocessor{
|
|||
Object.keys(this.emotes).forEach((key) => {
|
||||
//For each emote in the current list
|
||||
this.emotes[key].forEach((emote) => {
|
||||
//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}ㅤ`);
|
||||
//Inject emote links into the message, pad with invisible whitespace to keep link from getting mushed
|
||||
this.message = this.message.replaceAll(`[${emote.name}]`, `ㅤ${emote.link}ㅤ`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ class canopyUtils{
|
|||
//Grab text content and send that shit out
|
||||
return outNode.documentElement.textContent;
|
||||
}
|
||||
|
||||
escapeRegex(string){
|
||||
/* I won't lie this line was whole-sale ganked from stack overflow like a fucking skid
|
||||
In my defense I only did it because browser-devs are taking fucking eons to implement RegExp.escape()
|
||||
This should be replaced once that function becomes available in mainline versions of firefox/chromium:
|
||||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/escape */
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
}
|
||||
|
||||
class canopyUXUtils{
|
||||
|
|
|
|||
Loading…
Reference in a new issue