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
|
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){
|
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});
|
||||||
|
|
|
||||||
|
|
@ -187,15 +187,41 @@ p.tooltip, h3.tooltip{
|
||||||
.spoiler:not(:hover){
|
.spoiler:not(:hover){
|
||||||
color: black;
|
color: black;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
|
filter: brightness(0);
|
||||||
|
|
||||||
img{
|
.interactive, a, img, video{
|
||||||
color: black;
|
color: black;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
filter: brightness(0);
|
filter: brightness(0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.interactive, a{
|
.strikethrough{
|
||||||
color: black;
|
text-decoration: line-through;
|
||||||
background-color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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){
|
postprocess(chatEntry, rawData){
|
||||||
|
//Create empty array to hold filter spans
|
||||||
|
this.filterSpans = [];
|
||||||
|
//Set raw message data
|
||||||
this.rawData = rawData;
|
this.rawData = rawData;
|
||||||
//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/chunk
|
||||||
this.splitMessage();
|
this.splitMessage();
|
||||||
|
|
||||||
//Inject links into un-processed placeholders
|
this.processQoute();
|
||||||
|
|
||||||
|
//Re-Hydrate and Inject links and embedded media into un-processed placeholders
|
||||||
this.processLinks();
|
this.processLinks();
|
||||||
|
|
||||||
//Inject clickable command examples
|
//Inject clickable command examples
|
||||||
|
|
@ -40,15 +44,24 @@ class chatPostprocessor{
|
||||||
//Inject clickable usernames
|
//Inject clickable usernames
|
||||||
this.processUsernames();
|
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();
|
this.addWhitespace();
|
||||||
|
|
||||||
//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();
|
||||||
|
|
||||||
|
|
@ -64,8 +77,9 @@ class chatPostprocessor{
|
||||||
//This also means all text should be added to element via textContent and *NOT* innerHTML
|
//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
|
//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
|
//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.
|
//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
|
//for each word in the splitstring
|
||||||
splitString.forEach((string) => {
|
splitString.forEach((string) => {
|
||||||
|
|
@ -90,6 +104,10 @@ class chatPostprocessor{
|
||||||
if(wordObj.type == 'word'){
|
if(wordObj.type == 'word'){
|
||||||
//Create span node
|
//Create span node
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
|
|
||||||
|
//Set span filter classes
|
||||||
|
span.classList.add(...wordObj.filterClasses);
|
||||||
|
|
||||||
//Set span text
|
//Set span text
|
||||||
span.textContent = wordObj.string;
|
span.textContent = wordObj.string;
|
||||||
|
|
||||||
|
|
@ -98,7 +116,7 @@ class chatPostprocessor{
|
||||||
}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');
|
||||||
link.classList.add('chat-link');
|
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||||||
link.href = wordObj.link;
|
link.href = wordObj.link;
|
||||||
//Use textContent to be safe since links can't be escaped serverside
|
//Use textContent to be safe since links can't be escaped serverside
|
||||||
link.textContent = wordObj.link;
|
link.textContent = wordObj.link;
|
||||||
|
|
@ -108,7 +126,7 @@ class chatPostprocessor{
|
||||||
}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');
|
||||||
badLink.classList.add('chat-dead-link', 'danger-link');
|
badLink.classList.add('chat-dead-link', 'danger-link', ...wordObj.filterClasses);
|
||||||
badLink.href = wordObj.link;
|
badLink.href = wordObj.link;
|
||||||
//Use textContent to be safe since links can't be escaped serverside
|
//Use textContent to be safe since links can't be escaped serverside
|
||||||
badLink.textContent = wordObj.link;
|
badLink.textContent = wordObj.link;
|
||||||
|
|
@ -118,7 +136,7 @@ class chatPostprocessor{
|
||||||
}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');
|
||||||
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)
|
//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
|
//arguably we could sanatize malformed links serverside since they're never actually used as links
|
||||||
malformedLink.textContent = wordObj.link;
|
malformedLink.textContent = wordObj.link;
|
||||||
|
|
@ -128,7 +146,7 @@ class chatPostprocessor{
|
||||||
}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');
|
||||||
img.classList.add('chat-img');
|
img.classList.add('chat-img', ...wordObj.filterClasses);
|
||||||
img.src = wordObj.link;
|
img.src = wordObj.link;
|
||||||
|
|
||||||
//Look for an emote by link since emotes are tx'd as bare links
|
//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'){
|
}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');
|
||||||
vid.classList.add('chat-video');
|
vid.classList.add('chat-video', ...wordObj.filterClasses);
|
||||||
vid.src = wordObj.link;
|
vid.src = wordObj.link;
|
||||||
vid.controls = false;
|
vid.controls = false;
|
||||||
vid.autoplay = true;
|
vid.autoplay = true;
|
||||||
|
|
@ -166,7 +184,7 @@ class chatPostprocessor{
|
||||||
//Create link node
|
//Create link node
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
//Set class
|
//Set class
|
||||||
link.classList.add('chat-link');
|
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||||||
//Set href and inner text
|
//Set href and inner text
|
||||||
link.href = "javascript:";
|
link.href = "javascript:";
|
||||||
link.textContent = wordObj.command;
|
link.textContent = wordObj.command;
|
||||||
|
|
@ -180,7 +198,7 @@ class chatPostprocessor{
|
||||||
//Create link node
|
//Create link node
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
//set class
|
//set class
|
||||||
link.classList.add(wordObj.color);
|
link.classList.add(wordObj.color, ...wordObj.filterClasses);
|
||||||
//Set href and inner text
|
//Set href and inner text
|
||||||
link.href = "javascript:";
|
link.href = "javascript:";
|
||||||
link.textContent = wordObj.string;
|
link.textContent = wordObj.string;
|
||||||
|
|
@ -194,7 +212,7 @@ class chatPostprocessor{
|
||||||
//Create link node
|
//Create link node
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
//set class
|
//set class
|
||||||
link.classList.add('chat-link');
|
link.classList.add('chat-link', ...wordObj.filterClasses);
|
||||||
//Set href and inner text
|
//Set href and inner text
|
||||||
link.href = `/c/${wordObj.chan}`;
|
link.href = `/c/${wordObj.chan}`;
|
||||||
link.textContent = wordObj.string;
|
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(){
|
processCommandExamples(){
|
||||||
//for each word object in the body
|
//for each word object in the body
|
||||||
this.messageArray.forEach((wordObj, wordIndex) => {
|
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)
|
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
|
||||||
const foundSpoilers = [];
|
const foundFilters = [];
|
||||||
//Spoiler detection stage
|
//Spoiler detection stage
|
||||||
//For each word object in the message array
|
//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
|
//Get the current word object
|
||||||
const wordObj = this.messageArray[wordIndex];
|
const wordObj = this.messageArray[wordIndex];
|
||||||
|
|
||||||
//If its a regular word and contains '##'
|
//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
|
//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 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
|
//ignore it and continue on to the next word object
|
||||||
continue main;
|
continue main;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Crawl throw word objects after the current one, not sure why wordIndex is saved as a string.. Thanks JS
|
//Crawl throw word objects after the current one
|
||||||
for(let endIndex = (Number(wordIndex) + 1); endIndex < this.messageArray.length; endIndex++){
|
for(let endIndex = (wordIndex + 1); endIndex < this.messageArray.length; endIndex++){
|
||||||
//Get the current end object
|
//Get the current end object
|
||||||
const endObj = this.messageArray[endIndex];
|
const endObj = this.messageArray[endIndex];
|
||||||
|
|
||||||
//If its a regular word and contains '##'
|
//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
|
//Scrape out delimiters
|
||||||
wordObj.string = wordObj.string.replaceAll("##",'');
|
wordObj.string = wordObj.string.replaceAll(delimiter,'');
|
||||||
endObj.string = endObj.string.replaceAll("##",'');
|
endObj.string = endObj.string.replaceAll(delimiter,'');
|
||||||
//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]});
|
//Add it to the list of detected filters
|
||||||
|
foundFilters.push(foundFilter);
|
||||||
|
|
||||||
|
//Run the filter callback
|
||||||
|
cb(foundFilter)
|
||||||
|
|
||||||
//Break the nested end-detection loop
|
//Break the nested end-detection loop
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -414,8 +447,42 @@ class chatPostprocessor{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add found spoilers to filters list
|
return foundFilters;
|
||||||
this.filterSpans = this.filterSpans.concat(foundSpoilers);
|
}
|
||||||
|
|
||||||
|
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(){
|
processLinks(){
|
||||||
|
|
@ -437,7 +504,8 @@ class chatPostprocessor{
|
||||||
//but we also don't want to clobber any surrounding whitespace
|
//but we also don't want to clobber any surrounding whitespace
|
||||||
string: wordObj.string.replace(`␜${linkIndex}`, '␜'),
|
string: wordObj.string.replace(`␜${linkIndex}`, '␜'),
|
||||||
link: link.link,
|
link: link.link,
|
||||||
type: link.type
|
type: link.type,
|
||||||
|
filterClasses: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -482,6 +550,15 @@ class chatPostprocessor{
|
||||||
}else if(this.rawData.type == "spoiler"){
|
}else if(this.rawData.type == "spoiler"){
|
||||||
//Set whole-body spoiler
|
//Set whole-body spoiler
|
||||||
this.chatBody.classList.add("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) => {
|
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, add invisible whitespace to the end to keep next character from mushing into the 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}ㅤ`);
|
this.message = this.message.replaceAll(`[${emote.name}]`, `ㅤ${emote.link}ㅤ`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,14 @@ class canopyUtils{
|
||||||
//Grab text content and send that shit out
|
//Grab text content and send that shit out
|
||||||
return outNode.documentElement.textContent;
|
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{
|
class canopyUXUtils{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue