Dette script bør ikke installeres direkte. Det er et bibliotek, som andre scripts kan inkludere med metadirektivet // @require https://update.greasyfork.org/scripts/447081/1199231/BP%20Funcs.js
// ==UserScript==
// @name BP Funcs
// @description Small script to be @require-d, providing useful functions and extensions I like to regularly refer to
// @version 1.1.0
// @namespace BP
// @author Benjamin Philipp <dev [at - please don't spam] benjamin-philipp.com>
// ==/UserScript==
/*
BP Funcs, as of 2023-06-02 17:22:41 (GMT +02:00)
*/
class SelectorDef{
constructor(selectors="", refineFunc=null, within=null){
this.selectors = [];
if(selectors){
switch(typeof selectors){
case "object":
if(selectors instanceof Array){
this.selectors = [...selectors];
}
else{
try{
Object.assign(this, selectors);
return this;
}catch(e){
console.warn("Could not assign Object to SelectorDef with", selectors);
}
}
break;
case "string":
this.selectors = [selectors];
break;
default:
console.warn("Could not construct SelectorDef with selector of type '" + (typeof selectors) + "':", selectors);
}
}
this.refineFunc = refineFunc;
this.within = within;
return this;
}
add(selectors="", refineFunc=null, within=null){
this.selectors.push(new SelectorDef(selectors, refineFunc, within));
return this;
}
find(within=null, separate=false){
if(!within)
within = document;
var r;
if(separate)
r = { length: 0, hasEMpty: false };
else
r = $();
var i = 0;
for(let sel of this.selectors){
let f = null;
if(sel instanceof SelectorDef){
f = sel.find(within);
}
else{
f = $(within).find(sel);
if(this.refineFunc)
f = this.refineFunc(f);
}
if(separate){
if(f && f.length)
r[i] = f;
else{
r[i] = null;
r.hasEMpty = true;
}
r.length++;
}
else if(f && f.length)
r = r.add(f);
i++;
}
return r;
}
get(separate = false){
return this.find(this.within, separate);
}
waitFor(cb, stopForAny = false, requireAll = false, cbFail=null, doneClass="", interval=200, maxTries=50){
console.log("waiting, maxTries = " + maxTries);
var res = this.get(requireAll);
var runAgain = true;
if(res.length>0){
runAgain = maxTries <= -1 && !stopForAny;
if(requireAll){
if(!res.hasEMpty){
var results = res[0];
console.log("got all?", res);
for(let i = 1; i<res.length; i++)
results = results.add(res[i]);
cb(results);
}
else
runAgain = true;
}
else
cb(res);
}
else
runAgain = true;
if(runAgain){
var t = this;
if(maxTries>0){
maxTries--;
} else if(maxTries==0){
if(typeof cbFail == "function")
cbFail();
return;
}
this.__timer = setTimeout(function(){
t.waitFor(cb, stopForAny, requireAll, cbFail, doneClass, interval, maxTries);
}, interval);
}
}
stop(){
if(this.__timer)
clearTimeout(this.__timer);
}
}
String.prototype.after = function(str, fromRight, returnAll){
if(fromRight === undefined)
fromRight = false;
if(returnAll === undefined)
returnAll = false;
var os = this.indexOf(str);
if(fromRight)
os = this.lastIndexOf(str);
if(os<0)
return returnAll?this:"";
return this.substring(os + str.length);
};
function arraySortRelevance(arr, q, selectRelevantProperty = (item)=>item){
q = q.toLowerCase().replace(/&/g, " and ");
var words = {
q: q.split(/[\b\s./\\'-]/)
};
var matchFunc = function(a, b){
return a.localeCompare(b, undefined, {sensitivity: "accent", usage: "sort" })===0;
};
arr.sort(function(itemA, itemB){
var items = {
a: selectRelevantProperty(itemA).toLowerCase().replace(/&/g, " and "),
b: selectRelevantProperty(itemB).toLowerCase().replace(/&/g, " and ")
};
if(matchFunc(items.a, items.b)){
return 1;
}
if(matchFunc(items.a, q))
return -1;
if(matchFunc(items.b, q))
return 1;
var score = {
a: 0,
b: 0,
last: {
a: 0,
b: 0
}
};
var exactMult = 2;
var runMult = 2;
var missingMult = 1;
words.a = items.a.split(/[\b\s./\\'-]/);
words.b = items.b.split(/[\b\s./\\'-]/);
for(let k of["a", "b"]){
for(let w of words.q){
if(w.trim()==="")
continue;
missingMult = 1;
var mi = items[k].indexOf(w);
if(mi>=0){
var s = w.length;
var ni = mi + s;
var prev = items[k].substring(mi-1, mi);
var next = items[k].substring(ni, ni+1);
if(! /\w/.test(prev + next)){
s *= exactMult;
}
score[k] += score.last[k] + s;
score.last[k] = w.length * (runMult - 1);
}
else{
score[k] -= w.length * missingMult;
score.last[k] = 0;
}
}
missingMult = 0.5;
for(let wk of words[k]){
if(!q.indexOf(wk)>=0){
score[k] -= wk.length * missingMult;
}
}
}
// log(score, items, q);
if(score.a>score.b)
return -1;
if(score.a<score.b)
return 1;
if(items.a.length<items.b.length)
return -1;
else
return 1;
});
return arr;
}
String.prototype.before = function(str, fromRight, returnAll){
if(fromRight === undefined)
fromRight = false;
if(returnAll === undefined)
returnAll = false;
var os = this.indexOf(str);
if(fromRight)
os = this.lastIndexOf(str);
if(os<0)
return returnAll?this:"";
return this.substr(0, os);
};
function bpMenu(style="light"){
var r = {};
r.obj = null;
r.items = {};
const styles = {
light: {
colors : {
background : "#fff",
color : "#333",
item : "#222",
itemHover : "#268",
itemBack : "transparent",
frame : "#eee",
button:{
background: "#bbb",
color: "#333",
background_hover: "#157",
color_hover: "#fff",
}
},
fontSize: "14px"
},
dark: {
colors : {
background : "#000",
color : "#ccc",
item : "#ddd",
itemHover : "#6ce",
itemBack : "transparent",
frame : "#111",
button:{
background: "#444",
color: "#ccc",
background_hover: "#7cf",
color_hover: "#000",
}
},
fontSize: "14px"
}
};
r.style = styles[style];
if(!r.style)
r.style = styles.light;
r.css = `
.bpbutton{
padding: 5px;
display: inline-block;
background: ${r.style.colors.button.background};
color: ${r.style.colors.button.color};
cursor: pointer;
font-weight: 600;
line-height: 1em;
}
.bpbutton:hover, .bpbutton.on{
background: ${r.style.colors.button.background_hover};
color: ${r.style.colors.button.color_hover};
}
#bpMenu{
position: fixed;
z-index: 99999;
top: -50px;
right: 0px;
height: 70px;
display: inline-block;
transition: top 0.5s;
padding: 0px 0px 10px;
}
#bpMenu .inner{
display: inline-block;
background-color: ${r.style.colors.background};
color: ${r.style.colors.color};
padding: 0px 10px 5px;
border-radius: 0 0 10px 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
#bpMenu:hover{
top: 0px;
}
#bpMenu .bp{
display: inline-block;
padding: 5px;
font-size: ${r.style.fontSize};
}
#bpMenu .bp.item{
color: ${r.style.colors.item};
background: ${r.style.colors.itemBack};
font-weight: bold;
cursor: pointer;
}
#bpMenu .bp.item:hover{
color: ${r.style.colors.itemHover};
}
#bpMenu .bp+.bp{
margin-left: 10px;
}
`;
r.setup = function(override=false){
var head, script;
head = document.getElementsByTagName("head")[0];
if(typeof $ !== "function"){
return console.error("bpMenu: No jQuery '$'; can't continue");
// console.log("jQuery not available?\nTrying to insert & load...", typeof $);
// script = document.createElement("script");
// script.type = "text/javascript";
// script.onload = function(){
// r.setup();
// };
// script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js";
// head.appendChild(script);
// return;
}
if(typeof bpModal !== "function"){
return console.error("bpMenu: No bpModal; can't continue");
// console.warn("BP Modal not available?\nTrying to insert & load...", typeof bpModal);
// script = document.createElement("script");
// script.type = "text/javascript";
// script.onload = function(){
// r.setup();
// };
// script.src = "https://benjamin-philipp.com/js/gm/funcs.js?funcs=bpModal";
// head.appendChild(script);
// return;
}
// console.log("Setup BP Menu");
if(override){
$("body>#bpMenu").remove();
}
r.injectStyles(override);
if(!$("body>#bpMenu").length)
$("body").append("<div id='bpMenu'><div class='inner'></div></div>");
r.obj = $("body>#bpMenu");
};
r.injectStyles = function(override=false){
if("undefined" != typeof GM_addStyle)
return GM_addStyle(r.css);
if(override)
$("#bpMenuStyle").remove();
if($("#bpMenuStyle").length<=0)
$("head").append(`<style id="bpMenuStyle">${r.css}</style>`);
};
r.add = function(id, html, cb=null, title="", override=false, sel=""){
let l = $("body>#bpMenu>.inner #" + id);
let add = true;
if(l.length >0){
add = false;
if(override){
l.remove();
add = true;
}
}
if(add){
if(title)
title = " title='" + title + "'";
$("body>#bpMenu .inner").append("<div id='" + id + "' class='bp" + (cb?" item":"") + "'" + title + ">" + html + "</div>");
r.items[id] = $("#bpMenu #" + id);
if(cb)
$("#bpMenu #" + id).click(function(e){
cb(e);
});
}
};
r.changeStyle = function(obj){
mergeDeep(r.style, obj);
r.injectStyles(true);
};
r.setup();
return r;
}
//if("undefined" === typeof bpMenuHelper){ // jshint ignore:line
// var bpMenuHelper = bpMenu(); // jshint ignore:line
//}
class BP_Color{
constructor(colorOrRed, green, blue){
this.color = {
red: 0,
green: 0,
blue: 0
};
if(colorOrRed instanceof Color){
this.color = colorOrRed.color;
}
else if(colorOrRed instanceof Array){
this.color = {
red: colorOrRed[0],
green: colorOrRed[1],
blue: colorOrRed[2]
};
}
else if(typeof colorOrRed == "object"){
this.color = {
red: colorOrRed.red || colorOrRed.Red || colorOrRed.r || colorOrRed.R,
green: colorOrRed.green || colorOrRed.Green || colorOrRed.g || colorOrRed.G,
blue: colorOrRed.blue || colorOrRed.Blue || colorOrRed.b || colorOrRed.B
};
}
else if(typeof colorOrRed == "string" && green === undefined){
var hex = [];
if(colorOrRed.startsWith("#")){
colorOrRed = colorOrRed.replace(/^#?(\w)(\w)(\w)$/, "#$1$1$2$2$3$3");
var c = Color.hexToRgb(colorOrRed);
if(c){
this.color.red = c[0];
this.color.green = c[1];
this.color.blue = c[2];
}
}
}
else{
this.color.red = colorOrRed;
this.color.green = green;
this.color.blue = blue;
}
return this;
}
toRGB(){
return [this.color.red, this.color.green, this.color.blue];
}
toHex(){
return Color.rgbToHex(this.color.red, this.color.green, this.color.blue);
}
get(){
return this.color;
}
static componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
static rgbToHex(r, g, b) {
return "#" + Color.componentToHex(r) + Color.componentToHex(g) + Color.componentToHex(b);
}
static hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
}
}
function colorString(str, colorOrRed, green, blue){
var color = [0,0,0];
if([colorOrRed, green, blue].every(v => typeof v == "number"))
color = [colorOrRed, green, blue];
else if(colorOrRed instanceof Array)
color = colorOrRed;
else{
color = new BP_Color(colorOrRed, green, blue).toRGB();
}
return `\x1B[38;2;${color[0]};${color[1]};${color[2]}m${str}\x1B[0m`;
}
function convolve(func, ...partArrays){
if(partArrays.length == 1 && !(partArrays[0] instanceof Array) && (typeof partArrays[0]) == "object")
partArrays = Object.values(partArrays[0]);
if(! partArrays?.length)
return;
var funcArgArrays = [];
var firstParts = partArrays[0];
var nextArgs = [];
if(partArrays.length == 1){
if(typeof func == "function"){
for(let p of firstParts)
func(p);
}
return firstParts;
}
var lastArray = false;
if(partArrays.length > 2){
nextArgs = convolve(false, ...partArrays.slice(1));
}
else{
nextArgs = partArrays[1];
lastArray = true;
}
for(let arg of firstParts){
for(let otherArgs of nextArgs){
if(lastArray)
funcArgArrays.push([arg, otherArgs]);
else
funcArgArrays.push([arg, ...otherArgs]);
if(typeof func == "function")
func(arg, ...otherArgs);
}
}
return funcArgArrays;
}
function copyToClipboard(text) {
if (window.clipboardData && window.clipboardData.setData) {
// Internet Explorer-specific code path to prevent textarea being shown while dialog is visible.
return window.clipboardData.setData("Text", text);
}
else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
var textarea = document.createElement("textarea");
textarea.textContent = text;
textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in Microsoft Edge.
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand("copy"); // Security exception may be thrown by some browsers.
msg("Copied.", "success", "", 1000);
return;
}
catch (ex) {
console.warn("Copy to clipboard failed.", ex);
return prompt("Copy to clipboard: Ctrl+C, Enter", text);
}
finally {
document.body.removeChild(textarea);
}
}
}
if("undefined" == typeof bp_iObject){
bp_iObject = {
style: `<style id="bp_iO_style">
div.bp_iObject{
color: #fff;
}
div.bp_iObject div.bp_iObject{
display: inline;
vertical-align: text-top;
}
.bp_iObject div.bp_iO_inner{
white-space: pre;
border: 1px solid transparent;
transition: all 0.5s;
}
.bp_iO_pseudoContent{
margin: 0.4em;
}
.bp_iO_pseudoContent + .bp_iO_pseudoContent{
margin-left: 0;
}
.bp_iO_pseudoContent:before{
content: attr(data-value);
}
span.bp_iO_meta{
opacity: 0.7;
background: #333;
border: 1px solid #888;
border-radius: 0.3em;
font-size: 66%;
}
span.bp_iO_indent:before{
content: " ";
white-space: pre;
}
span.bp_iO_comma{
display: inline;
vertical-align: bottom;
}
div.bp_iObject>.bp_iO_content{
color: #bbb;
white-space: pre;
}
div.bp_iObject[data-type="string"]>.bp_iO_content,
div.bp_iObject span.quotes{
color: #888;
}
span.bp_iO_key{
font-style: italic;
}
.collapsible>span.bp_iO_key{
cursor: pointer;
}
div.bp_iObject[data-type="string"]>.bp_iO_content>span.text{
color: #fe8;
}
div.bp_iObject[data-type="number"]>.bp_iO_content{
color: #5cf;
}
div.bp_iObject[data-type="RegExp"]>.bp_iO_content{
color: #f48;
}
div.bp_iObject[data-type="boolean"]>.bp_iO_content{
color: #08f;
}
div.bp_iObject.collapsed>.bp_iO_content{
white-space: normal;
}
div.bp_iObject.collapsed .bp_iO_inner{
display: inline-block;
width: 1px;
height: 1px;
overflow: hidden;
margin-left: -5px;
opacity: 0.01;
clip: rect(0 0 0 0);
}
div.bp_iObject.collapsible .bracket.closing{
cursor: pointer;
pointer-events: all;
}
div.bp_iObject.collapsed .bracket.closing:before{
content: "... ";
}
.bp_iObject{
position: relative;
}
.bp_iObject.collapsible>.bp_iO_key:before{
content: "â–² ";
position: absolute;
display: inline-block;
right: 100%;
font-size: 66%;
pointer-events: all;
}
.bp_iObject.collapsible.collapsed>.bp_iO_key:before{
content: "â–¼ ";
}
.bp_iO_inner.editing{
border: 1px inset #888;
background: #000;
color: #fff;
padding: 0.5em;
margin: 0.2em;
}
.bp_iO_copy{
position: absolute;
right: 0;
top:0;
opacity: 0;
padding: 0.75em 1em;
background: #666;
color: #fff;
border: 1px solid #fff;
border-radius: 0.5em;
cursor: pointer;
transition: all 0.5s;
}
/* .bp_iO_copy::before{
content: "\\002398 Copy";
}
.bp_iObject:hover>.bp_iO_copy{
opacity: 0.3;
}
.bp_iObject:hover>.bp_iO_copy:hover{
opacity: 1;
background: #000;
}*/ /* TODO: make palatable */
.zeroHeight{
line-height: 0;
margin:0;
padding:0;
display: none;
}
.collapsed br.zeroHeight{
display: inline;
}
.collapsedIndent{
display: none;
}
.collapsed>.bp_iO_content>.bp_iO_inner>.collapsedIndent{
display: inline;
}
</style>`,
createInteractiveObject: function(obj, onlyOwnProperties=true, indentString="\t", objKey="", level=0, includeLabel = false){
if("undefined" == typeof bp_iO)
bp_iO = bp_iObject.setup();
var indent = "";
for(i=0; i<level; i++)
indent += indentString;
var q = "<span class='quotes'>"</span>";
var ell = "<span class='bp_iO_ellipsis'>"</span>";
var r = "";
var t = typeof obj;
var typeString = t.replace(/^\w/, (a)=> a.toUpperCase());
var inner = "";
var count=0;
var isArray = obj instanceof Array;
switch(typeof obj){
case "object":
var pt = ""; // + obj.toString(); // TODO: prototype class name?
if(isArray)
typeString = "Array";
else if(obj instanceof RegExp){
typeString = t = "RegExp";
inner = obj.toString();
break;
}
typeString += " " + pt;
if(!obj || !obj.keys){
inner = obj.toString();
break;
}
var keys = Object.keys(obj);
if(onlyOwnProperties){
var okeys = keys;
keys = [];
for(let k of okeys){
if(obj.hasOwnProperty(k))
keys.push(k);
}
}
count = keys.length;
for(let i=0; i<keys.length; i++){
let k = keys[i];
inner += (isArray?"":`${indent + indentString}`) + createInteractiveObject(obj[k], onlyOwnProperties, indentString, k, level+1, isArray?false:true) + (i<keys.length-1?"<span class='bp_iO_comma'>, </span>" + (isArray?"":"<br />"):"");
}
if(!isArray && count>0)
inner = "<div class='bp_iO_inner'><br class='zeroHeight' />" + inner + "<br /><span class='collapsedIndent'>" + indent + "</span></div>";
if(isArray)
inner = "[" + inner + "]";
else{
inner = "{" + inner + (inner?indent:"") + "<span class='bracket closing'>}</span>";
}
break;
case "string":
inner += q + "<span class='text bp_iO_inner'>" + htmlEntities(obj) + "</span>" + q;
break;
default:
inner += "<span class='bp_iO_inner'>" + htmlEntities(obj.toString()) + "</span>";
}
r = (objKey && includeLabel?`<span class='bp_iO_key'>${q + objKey + q}</span><span class='bp_iO_type bp_iO_meta bp_iO_pseudoContent' data-value='${typeString}'></span>` + (t=="object"?`<span class='bp_iO_count bp_iO_meta bp_iO_pseudoContent' data-value='(${count})'></span>`:"") + ": " :"") + "<span class='bp_iO_content'>" + inner + "</span>";
r = `<div class="bp_iObject${t=="object" && !(obj instanceof Array) && count>0?" collapsible":""}${level<=0?" root editable":""}" data-type="${t}" data-level="${level}" data-key="${objKey}"><div class="bp_iO_copy"></div>${r}</div>`;
if(level <= 0){
r = $(r)[0];
r.bp_iO = {
ref: obj,
onlyOwnProperties,
indentString,
element: r,
redraw: function(){
var e = createInteractiveObject(this.ref, this.onlyOwnProperties, this.indentString);
this.element.innerHTML = e.innerHTML;
$(e).remove();
// console.log(this.element);
}
};
}
return r;
},
setup: function(){
if("undefined" == typeof bp_iO){
bp_iO = this;
$("head").append(this.style);
$("body").on("click", ".bp_iO_copy", function(e){
e.stopImmediatePropagation();
// console.log(e, e.target);
var r = $(e.target).parent().text();
copyToClipboard(r);
});
$("body").on("click", ".collapsible>.bp_iO_key, .collapsible>.bp_iO_content>.bracket.closing", function(e){
e.stopImmediatePropagation();
// console.log(e, e.target);
var p = $(e.target).parent();
if(p.hasClass("bp_iO_content"))
p = p.parent();
p.toggleClass("collapsed");
});
$("body").on("dblclick", ".bp_iObject.root.editable .bp_iO_inner", function(e){
// if(!$(e.target).is(e.currentTarget))
// return;
e.stopImmediatePropagation();
// console.log(e, e.target);
var o = $(e.currentTarget);
o.addClass("editing");
// console.log(e.target, o);
o.attr("contenteditable", "true");
o[0].beforeEdit = o.text().trim();
o[0].focus();
return false;
});
$(document).on("keydown", ".bp_iO_inner.editing", function(e){
// console.log(e);
var o = $(e.target);
if(e.key=="Enter" && !e.shiftKey){
// console.log("enter!");
var val_inner = o.text().trim();
var val_container = o.closest(".bp_iObject").first();
var val_outer = o.parent().text().trim();
// console.log("new value?", val_outer, val_inner);
var path = [];
var obj = o.parents(".bp_iObject.root");
if(!obj || obj.length<=0 || !obj[0].bp_iO){
console.error("Dang, no root found for", o);
}
else{
try{
obj = obj[0].bp_iO;
var ref = obj.ref;
var v = eval(`(function(){return ${val_outer};})();`);
if(val_container.is(o.parents(".bp_iObject.root").first())){
// console.log("yup, modifying root");
for(let k in ref) // TODO: accommodate different types
delete ref[k];
for(let k in v)
ref[k] = v[k];
obj.redraw();
}
else{
var ancestry = o.parentsUntil(".bp_iObject.root", ".bp_iObject").get();
ancestry = ancestry.reverse();
for(let i = 0; i<ancestry.length; i++){
var p = ancestry[i];
// var key = p.attr("data-key");
var k = p.dataset.key;
// console.log(k, p);
if(i>=ancestry.length-1){
// console.log(`(function(){return ${val_outer};})();`);
ref[k] = v;
var h = createInteractiveObject(ref[k], obj.onlyOwnProperties, obj.indentString, k, i+1, i+1>0);
val_container.html(h);
break;
}
ref = ref[k];
path.push(k);
}
// console.log(obj, path)
}
}catch(e){
console.error(e);
}
}
}
else if(e.key=="Escape"){
// console.log("Esc!");
o.text(o[0].beforeEdit);
}
else
return;
e.preventDefault();
o.removeClass("editing");
o.attr("contenteditable", "false");
});
createInteractiveObject = this.createInteractiveObject;
return this;
}
}
};
}
function dateString(date, format){
if(date===undefined || date===null || date === "")
date = new Date();
if(format===undefined)
format="YYYY-MM-DD HH:mm:SS";
else if(format==="file")
format="YYYY-MM-DD HH-mm-SS";
date = new Date(date);
var year = pad(date.getFullYear(), 4);
var months = pad(date.getMonth() + 1);
var days = pad(date.getDate());
var hours = pad(date.getHours());
var minutes = pad(date.getMinutes());
var seconds = pad(date.getSeconds());
return format.replace("YYYY", year)
.replace("MM", months)
.replace("DD", days)
.replace("HH", hours)
.replace("mm", minutes)
.replace("SS", seconds);
}
function pad(num, digits=2){
var r = String(num);
if(r.padStart)
return r.padStart(digits, "0");
return ("0000000000" + r).slice(-digits);
}
function escapeHtml(str){
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
}
String.prototype.escapeHtml = function(){
return escapeHtml(this);
};
filterTables = {count: 0, hasInit: false};
filterTables.init = function(filter="", override=false){
if($("head #bp_filtertable").length<=0)
$("head").append(`
<style id="bp_filtertable">
.filtertable.table,
.filtertable .table {
display: table;
}
.filtertable .tr {
display: table-row;
}
.filtertable .td,
.filtertable .th {
display: table-cell;
}
.filtertable th,
.filtertable .th {
white-space: nowrap;
word-break: keep-all;
padding: 3px;
text-align: left;
font-weight: bold;
}
.filtertable .filterhide, .simplehide{
display: none;
}
.filtertable th.filtering{
background-color: rgba(200,20,0,0.3);
}
.filtertable .columnFilter{
display: inline-block;
vertical-align: middle;
width: calc(100% - 20px);
}
.filtertable .columnFilter::placeholder{
font-weight: 300;
}
.filtertable .sorter{
display: inline-block;
vertical-align: middle;
margin-left: 3px
}
.filtertable .sort{
padding: 1px 4px;
line-height: 9px;
font-size: 10px;
background-color: rgba(140,140,140,0.5);
color: #ccc;
cursor: pointer;
opacity: 0.5;
}
.filtertable .sort+.sort{
margin-top:2px;
}
.filtertable .sort.desc:after{
content: "â–¼";
}
.filtertable .sort.asc:after{
content: "â–²";
}
.filtertable .sort:hover{
opacity: 1;
}
.filtertable .sort.active{
background-color: #160;
color: #fff;
}
.filtertable .clearfilter{
display: inline-block;
vertical-align: middle;
margin-left: -15px;
padding: 0 3px 4px;
background: rgba(120,10,0,1);
color: #fff;
opacity: 0.5;
cursor: pointer;
}
.filtertable .clearfilter:hover{
opacity: 1;
}
</style>
`);
$(".filtertable" + filter + ":not(.hasSetup)").each(function(){
makeTableSortable(this, true);
});
if(filterTables.hasInit && !override)
return;
$("body").on("keyup", ".filtertable .columnFilter", function(e){
// console.log(this, e);
var t = $(this).closest("table, .table");
if(!t || t.length<1)
return console.error("Table not found filtering");
var headers = t[0].filterHeaders;
if(!headers)
headers = t;
var cont = t.children("tbody, .tbody");
if(cont && cont.length>0)
t = cont.first();
var n = $(this).attr("data-colnum");
var o = this;
clearTimeout(filterTables.tuFilter);
filterTables.tuFilter = setTimeout(function(){
// console.log("filtering", n, t);
var f = o.value;
var fheader = $(headers).find("th:nth-child(" + n + "), .th:nth-child(" + n + ")");
if(f == ""){
$(t).find("tr, .tr").each(function(){
if(filterTables.setColumnFilter(this, n, false))
filterTables.showhideFilter(this);
});
$(fheader).removeClass("filtering");
return;
}
$(fheader).addClass("filtering");
$(t).find("tr, .tr").each(function(){
if(this == fheader || $(this).hasClass("sortHeaderRow"))
return;
var c = $(this).find("td:nth-child(" + n + "), .td:nth-child(" + n + ")");
// console.log("got cells?", c);
if(c.length > 0){
var s = c.text();
// console.log("compare", f, "against", s, "in", n+" -> nth child");
var fs = f.substr(0, 1);
var fc = true;
if(fs == "<" && f.length > 1){
// console.log("is <");
fc = s * 1 >= f.substr(1) * 1;
} else if(fs == ">" && f.length > 1){
// console.log("is >");
fc = s * 1 <= f.substr(1) * 1;
} else {
fc = !s.toLowerCase().includes(f.toLowerCase());
}
if(filterTables.setColumnFilter(this, n, fc)){
filterTables.showhideFilter(this);
}
}
});
}, 100);
});
$("body").on("click", ".filtertable .sort", function(){
var isactive = !$(this).hasClass("active");
filterTables.intable(this, ".sort").removeClass("active");
$(this).toggleClass("active", isactive);
var sortcol = 0;
var asc = true;
if(isactive){
sortcol = $(this).parent().attr("data-colnum");
asc = $(this).hasClass("asc");
}
filterTables.sortTable($(this).closest("table, .table").first(), sortcol, asc);
});
$("body").on("click", ".clearfilter", function(){
var fi = $(this).parent().find(".columnFilter");
// console.log("clear", fi);
// if(fi.length>0){
$(fi).val("");
$(fi).keyup();
// }
});
$("body").on("click", "table.minimized tr, .table.minimized .tr", function(){
$(this).parent().find("tr.active, .tr.active").removeClass("active");
$(this).addClass("active");
});
setTimeout(filterTables.resizeFilters, 1000);
setInterval(filterTables.resizeFilters, 5000);
filterTables.hasInit = true;
};
filterTables.tuFilter = null;
filterTables.setColumnFilter = function(e, c, onoff){
var cols = [];
var ec = $(e).attr("filteredCols");
if(undefined !== ec && ec != "")
cols = ec.split(",");
var i = cols.indexOf(c);
// console.log("Set filter for column " + c + " to " + onoff.toString() + " on: ", e);
if(i < 0 == onoff){
if(onoff){
cols.push(c);
// console.log("column filter added");
} else {
// console.log("remove column filter:");
var a = cols.splice(i, 1);
// console.log("a:", a);
// console.log("cols:", cols);
}
$(e).attr("filteredCols", cols.join(","));
// console.log("Needed change, is now: " + $(e).attr("filteredCols"));
return true;
}
// console.log("no change");
return false;
};
filterTables.showhideFilter = function(e){
var ec = $(e).attr("filteredCols");
if(undefined !== ec && ec != "")
$(e).addClass("filterhide");
else
$(e).removeClass("filterhide");
};
filterTables.sortTable = function(table, col, asc){
var tbody = $(table).children("tbody, .tbody");
if(tbody.length>0)
table = tbody;
var tosort = $(table).children("tr.sortable, .tr.sortable");
var ifasc = asc ? 1 : -1;
// console.log("sorting:", table, tosort);
tosort.sort(function(a, b){
var ca, cb;
if(col === 0){
ca = $(a).attr("nosort");
cb = $(b).attr("nosort");
} else {
ca = $(a).find("td:nth-child(" + col + "), .td:nth-child(" + col + ")").text();
cb = $(b).find("td:nth-child(" + col + "), .td:nth-child(" + col + ")").text();
}
ca = ca.trim().toLowerCase();
cb = cb.trim().toLowerCase();
if((ca*1).toString() == ca.toString() && (cb*1).toString() == cb.toString()){
// console.log("is numeric");
return (ca*1 - cb*1) * ifasc;
}
else{
var rex = /\s*(\d+)(.*)/;
var ma = ca.match(rex);
var mb = cb.match(rex);
if(ma && mb){
// console.log("oh, numbers!", ma, mb);
var diff = ma[1] - mb[1];
if(diff!=0)
return diff * ifasc;
}
}
if(ca > cb)
return 1 * ifasc;
if(ca < cb)
return -1 * ifasc;
return 0;
});
// $(table).children("tr.sortable, .tr.sortable").remove();
$(table).append(tosort);
};
filterTables.intable = function(el, s){
return $(el).closest("table, .table").first().find(s);
};
filterTables.resizeFilters = function(){
$(".shrink .columnFilter").each(function(){
var o = this;
filterTables.shrinkifbigger(o);
});
};
filterTables.shrinkifbigger = function(o){
var p = $(o).parent().parent();
var w = p.width() - 22;
if(w<40)
w = 40;
if(w > $(p).width()-20)
return;
$(o).css("width", w+"px");
// setTimeout(function(){
if(w > $(p).width()-20){
filterTables.shrinkifbigger(o);
// console.log("shrink: " + p.text());
}
else{
// console.log("is max: " + p.text);
}
// }, 1);
};
function makeTableSortable(element, hasInit=false){
var table = $(element);
var headers;
if(table.is("tr, .tr")){
headers = table;
table = headers.closest("table, .table");
}
else{
let cont = table.children("thead, .thead, tbody, .tbody").first();
if(!cont || cont.length<=0)
cont = table;
headers = cont.find(">tr>th, >.tr>th, >tr>.th, >.tr>.th");
if(headers && headers.length>0)
headers = headers.parent();
else
headers = cont.find(">tr, >.tr").first();
}
headers.addClass("sortHeaderRow").children("td, .td").addClass("th"); // in case they're just td, .td
table.addClass("filtertable sortable");
table[0].filterHeaders = headers;
filterTables.count++;
var c = 0;
$(headers).children("th, .th").each(function(){
var h = $(this).html();
c++;
var w = $(this).width() - 16;
if(w<40)
w = 40;
$(this).html("<div class='filterable'>" + h + "</div><div class='filters'><input class='columnFilter' style='widdth: " + w + "px;' name='columnFilter-" + c + "' value='' data-colnum='" + c + "' placeholder='Filter \"" + h + "\"' /><div class='clearfilter'>x</div><div class='sorter' data-colnum='" + c + "'><div class='sort asc'></div><div class='sort desc'></div></div></div>").addClass("sortHeader");
});
c = 0;
var cont = table.children("tbody, .tbody");
if(!cont || cont.length<1)
cont = table;
$(cont).children("tr:not(.sortHeaderRow), .tr:not(.sortHeaderRow)").each(function(){
// if(c > 0){
$(this).addClass("sortable");
$(this).attr("nosort", c);
// }
c++;
});
table.addClass("hasSetup");
if(!filterTables.hasInit && !hasInit)
filterTables.init();
}
function filteredDeepCopy(obj, func, maxDepth=10, circ=[]){
var out = {};
if(obj instanceof Array)
out = [];
for(let k in obj){
if(!Object.hasOwn(obj, k))
continue;
let v = obj[k];
if(typeof func == "function"){
let o = func(k, v);
if(!o)
continue;
if(o instanceof Array){
k = o[0];
v = o[1];
}
}
if(typeof v == "object"){
if(maxDepth<=0 || circ.includes(v))
continue;
maxDepth--;
out[k] = filteredDeepCopy(v, func, maxDepth, [...circ, v]);
continue;
}
out[k] = v;
}
return out;
}
var bpTitleFormats = {
movies: [
"[title] [[year]]",
"[title] ([year])",
"[title] - [year]"
],
series: [
"[name] - Season [season] Episode [episode] - [title]",
"[name] - Season [season] Episode [episode]",
"[name] - S[lzseason]E[lzepisode] - [title]",
"[name] - S[lzseason]E[lz3episode] - [title]"
]
};
var bpMediaTitleRegex = {
movies: [
/(.+) \[(\d{4})\]$/mi,
/(.+) \((\d{4})\)$/mi,
/(.+) - (\d{4})$/mi,
],
series: [
// /(.+?) ?-? (?:(?:season |S)?0?0?(\d+))? ?(?:episode |E|x)0*(\d+(?:-\d+)?)(?: ?[:-]? (.+))?/mi
/(.+?) ?[:,-]? (?:(?:season |S)?0*(\d+))?\s*[,-]?\s*(?:episode |E|x)0*(\d+(?:-\d+)?)(?: ?[:, -]* (.+))?/mi
]
};
function testEpisodeTitleRex(rex){
var values = {
name: ["Some series name", "Test? <^> \"Toast\""],
season: ["0", "03", "102"],
episode: ["0", "03", "102", "13-14"],
title: ["and a title", "\"and a title\"", " - and a title"],
joiners: [",", ":", " :", "-", " -", ""],
altjoiners: [",", "-", " -"],
sseason: ["season ", "s"],
sepisode: ["episode ", "e"]
};
if(!rex)
rex = bpMediaTitleRegex.series[0];
var testStrings = [];
convolve((name, season, episode, title, joiner, altjoiner, sseason, sepisode)=>{
testStrings.push(`${name}${joiner} ${sseason}${season}${altjoiner} ${sepisode}${episode}${joiner} ${title}`);
testStrings.push(`${name}${joiner} ${sseason}${season}${altjoiner}${sepisode}${episode}${joiner} ${title}`);
testStrings.push(`NoTitle${name}${joiner} ${sseason}${season}${altjoiner} ${sepisode}${episode}`);
testStrings.push(`NoTitle${name}${joiner} ${sseason}${season}${altjoiner}${sepisode}${episode}`);
testStrings.push(`${name} ${season}x${episode}${joiner} ${title}`);
testStrings.push(`NoTitle${name} ${season}x${episode}`);
},
values
);
// log(testStrings);
var colorOk = "#66ff55";
var colorWarn = "#cccc55";
var colorBad = "#ff6655";
var testOk = [];
var testFail = [];
for(let s of testStrings){
s = s.replace(/\s+/g, " ");
var m = s.match(rex);
// log(r);
if(!m || !m.length){
log(colorString(s, colorWarn), colorString("Failed RegEx test", colorBad));
testFail.push([s, "Failed to match RegEx"]);
}
else{
// log(colorString(s, colorOk));
var name = m[1];
var season = m[2];
var episode = m[3];
var title = m[4];
if(title){
title = title.trim().replace(/^[,: -]+/g, "");
title = title.trim().replace(/^"(.+)"$/, "$1");
}
var isNoTitle = false;
if(name.startsWith("NoTitle")){
name = name.replace(/^NoTitle/, "");
isNoTitle = true;
}
if(!values.name.includes(name)){
testFail.push([s, "Failed NAME"]);
continue;
}
if(!["0", "3", "102"].includes(season)){
testFail.push([s, "Failed SEASON"]);
continue;
}
if(!["0", "3", "102", "13-14"].includes(episode)){
testFail.push([s, "Failed EPISODE"]);
continue;
}
if(!["and a title"].includes(title) && !isNoTitle){
testFail.push([s, "Failed TITLE"]);
continue;
}
testOk.push(s);
}
}
for(let f of testFail)
log(f[0], colorString(f[1], colorBad));
log(colorString("Test OK: " + testOk.length, colorOk), colorString("Test Failed: " + testFail.length, testFail.length?colorBad:colorOk));
}
function guessMovieOrTV(title){
var tit = title.replace(/[—–]/g, "-"); // em-dash, en-dash
for(let rex of bpMediaTitleRegex.series){
if(rex.test(tit))
return "TV";
}
for(let rex of bpMediaTitleRegex.movies){
if(rex.test(tit))
return "Movie";
}
return false;
}
function formatMovieTV(tit, templateSeries, templateMovie){
switch(guessMovieOrTV(tit)){
case "TV":
return formatEpisodeTitle(tit, templateSeries);
case "Movie":
return formatMovieTitle(tit, templateMovie);
default:
console.log("Could not identify TV or Movie title");
return tit;
}
}
function formatMovieTitle(tit, template){
if(!template)
template = bpTitleFormats.movies[0];
// console.log("preferred format: " + template);
var match = false;
for(let rex of bpMediaTitleRegex.movies){
match = tit.match(rex);
if(match){
// match = rex.exec(tit);
console.log("title matches format " + rex.toString(), match);
break;
}
}
if(!match){
console.log("Title format not recognized", tit);
return tit;
}
var name = match[1];
var year = match[2];
tit = template.replace("[title]", name)
.replace("[year]", year);
console.log("formatted title:", tit);
return tit;
}
function formatEpisodeTitle(tit, template){
if(!template)
template = bpTitleFormats.series[0];
// console.log("preferred format: " + template);
tit = tit.replace(/[—–]/g, "-"); // em-dash, en-dash
var match = false;
for(let rex of bpMediaTitleRegex.series){
match = tit.match(rex);
if(match){
// match = rex.exec(tit);
console.log("title matches format " + rex.toString(), match);
break;
}
}
if(!match){
console.log("Title format not recognized", tit);
return tit;
}
var name = match[1];
var season = match[2];
if(!season)
season = 1;
var episode = match[3];
var title = (match.length>=5 && match[4] !== undefined)? match[4] : "";
if((/(Episode #? ?\d+|S\d+ ?E\d+)/i).test(title))
title = "";
// console.log({"name" : name, "season" : season, "episode" : episode, "title" : title});
if(title===""){
template = template.replace(/ ?-? \[title\]/, "");
console.log("no title:", template);
}
tit = template.replace("[name]", name)
.replace("[season]", season)
.replace("[episode]", episode)
.replace(/\[lz(\d*)season]/i, function(_,p){
if(p==="")
p = 2;
return lz(season, p);
})
.replace(/\[lz(\d*)episode]/i, function(_,p){
if(p==="")
p = 2;
return lz(episode, p);
})
.replace("[title]", title);
console.log("formatted title:", tit);
return tit;
}
function sanitize(str){
str = str.replace(/[\\]/g, "-")
.replace(/["]/g, "'")
.replace(/\?\?/g, "â‡")
.replace(/\?/g, "︖")
.replace(/\s*[/:]\s*/g, " - ")
.replace(/\s+-\s*(?:-+\s+)+/g, " - ")
.replace(/\s+/g, " ");
return str.trim();
}
function lz(num, places = 2){
return ("0000000000" + num).slice(-places);
}
function varToPretty(str, casing="title", isSecond=false){
str = str
.replace(/(?:([^A-Z])([A-Z]))|(?:([a-zA-Z])([^a-zA-Z]))/g, "$1$3 $2$4")
.replace(/_/g, " ")
.replace(/\s\s+/g, " ")
.replace(/(max|min)/gi, "$1imum");
if(casing == "title")
str = str.replace(/(^|\s+)([a-z])/g, (_, a, b) => a + b.toUpperCase());
if(isSecond)
return str;
return varToPretty(str, casing, true);
}
function toTitleCase(str, preserveCaps=false, preserveAllCaps=false){
return str.replace(/\w[^\s_:-]*/g, function(txt){
var rest = txt.substr(1);
if(!preserveCaps){
if(preserveAllCaps){
if(txt.charAt(0) != txt.charAt(0).toUpperCase()|| rest != rest.toUpperCase())
rest = rest.toLowerCase();
}
else
rest = rest.toLowerCase();
}
return txt.charAt(0).toUpperCase() + rest;
});
}
const mimeTypes = {
".aac": "audio/aac",
".abw": "application/x-abiword",
".arc": "application/x-freearc",
".avif": "image/avif",
".avi": "video/x-msvideo",
".azw": "application/vnd.amazon.ebook",
".bin": "application/octet-stream",
".bmp": "image/bmp",
".bz": "application/x-bzip",
".bz2": "application/x-bzip2",
".cda": "application/x-cdf",
".csh": "application/x-csh",
".css": "text/css",
".csv": "text/csv",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".eot": "application/vnd.ms-fontobject",
".epub": "application/epub+zip",
".gz": "application/gzip",
".gif": "image/gif",
".htm": "text/html",
".html": "text/html",
".ico": "image/vnd.microsoft.icon",
".ics": "text/calendar",
".jar": "application/java-archive",
".jpeg.jpg": "image/jpeg",
".js": "text/javascript",
".json": "application/json",
".jsonld": "application/ld+json",
".mid.midi": "audio/midi",
".mjs": "text/javascript",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".mpeg": "video/mpeg",
".mpkg": "application/vnd.apple.installer+xml",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".oga": "audio/ogg",
".ogv": "video/ogg",
".ogx": "application/ogg",
".opus": "audio/opus",
".otf": "font/otf",
".png": "image/png",
".pdf": "application/pdf",
".php": "application/x-httpd-php",
".ppt": "application/vnd.ms-powerpoint",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".rar": "application/vnd.rar",
".rtf": "application/rtf",
".sh": "application/x-sh",
".svg": "image/svg+xml",
".swf": "application/x-shockwave-flash",
".tar": "application/x-tar",
".tif": "image/tiff",
".tiff": "image/tiff",
".ts": "video/mp2t",
".ttf": "font/ttf",
".txt": "text/plain",
".vsd": "application/vnd.visio",
".wav": "audio/wav",
".weba": "audio/webm",
".webm": "video/webm",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".xhtml": "application/xhtml+xml",
".xls": "application/vnd.ms-excel",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xml": "application/xml",
".xul": "application/vnd.mozilla.xul+xml",
".zip": "application/zip",
".3gp": "video/3gpp",
".3g2": "video/3gpp2",
".7z": "application/x-7z-compressed"
};
function getContentTypeByExtension(ext){
var defaultType = "application/octet-stream";
if(typeof ext != "string")
return console.warn('[getContentTypeByExtension] Invalid type supplied, please supply string', ext) || defaultType;
if(ext.indexOf(".")<0)
ext = "." + ext;
else
ext = ext.replace(/^.*(?=[.]\w+$)/, '');
var mime = mimeTypes[ext.toLowerCase()];
if(!mime)
return console.warn('[getContentTypeByExtension] Failed to resolve for content name: %s', ext) || defaultType;
return mime;
}
function getParam(s){
return getParamFromString(location.href, s);
}
function getParamFromString(u, s){
var url = new URL(u);
return url.searchParams.get(s);
}
function getProperties(obj, filter=false, skipNative = true, _stringify=false){
var r = {};
var i = 0;
for(let k in obj){
let v = obj[k];
let include = true;
switch(typeof filter){
case "function":
include = filter(v);
break;
case "object":
if(filter instanceof RegExp)
include = filter.test(k);
break;
case "string":
let types = filter.split(/[, ]/);
include = false;
let inverted = false;
for(let t of types){
t = t.trim();
if(t == "")
continue;
if(t.substr(0,1)=="!"){
inverted = true;
t = t.substr(1);
}
if((t == "function" && v && v.call)){
if(!inverted){
v = v.toString();
include = !skipNative || v.indexOf("[native code]")<0;
}
else
include = false;
break;
}
else if((t == typeof v) ^ inverted){
include = true;
break;
}
}
break;
}
if(include){
if(_stringify && (typeof v != "string")){
let maybeRemoveQuotes = false;
try{
v = JSON.stringify(v, function(k, v){
if(typeof v == "function" || (v && v.call)){
v = v.toString();
maybeRemoveQuotes = true;
return v;
}
return v;
}, "\t");
}catch(e){
maybeRemoveQuotes = false;
v = v.toString();
}
if(maybeRemoveQuotes && typeof v == "string" && v.startsWith("\"") && v.endsWith("\""))
v = v.substring(1, v.length-1);
}
r[k] = v;
}
}
return r;
}
function getSelectedElements(){
var allSelected = [];
try{
var selection = window.getSelection();
var range = selection.getRangeAt(0);
if(range.startOffset == range.endOffset)
return allSelected;
var cont = range.commonAncestorContainer;
if(!cont){
// console.log("no parent container?");
return range.startContainer;
}
if(!cont.nodeName || cont.nodeName == "#text" || !cont.getElementsByTagName){
var p = cont.parentElement;
// console.log("weird container or text node; return parent", cont, p);
if(!p){
// console.log("actually, never mind; has no parent. Return element instead");
return [cont];
}
return [p];
}
var allWithinRangeParent = cont.getElementsByTagName("*");
for (var i=0, el; el = allWithinRangeParent[i]; i++){ // jshint ignore:line
// The second parameter says to include the element
// even if it's not fully selected
if (selection.containsNode(el, true))
allSelected.push(el);
}
}catch(e){
console.log(e);
}
return allSelected;
}
function htmlEntities(str, nl2br=false){
str = str.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
return '&#' + i.charCodeAt(0) + ';';
});
if(nl2br)
str = str.replace(/\r?\n/g, "<br />");
return str;
}
function isNativeFunction(value) {
// Used to resolve the internal `[[Class]]` of values
var toString = Object.prototype.toString;
// Used to resolve the decompiled source of functions
var fnToString = Function.prototype.toString;
// Used to detect host constructors (Safari > 4; really typed array specific)
var reHostCtor = /^\[object .+?Constructor\]$/;
// Compile a regexp using a common native method as a template.
// We chose `Object#toString` because there's a good chance it is not being mucked with.
var reNative = RegExp('^' +
// Coerce `Object#toString` to a string
String(toString)
// Escape any special regexp characters
.replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&')
// Replace mentions of `toString` with `.*?` to keep the template generic.
// Replace thing like `for ...` to support environments like Rhino which add extra info
// such as method arity.
.replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
);
var type = typeof value;
return type == 'function'
// Use `Function#toString` to bypass the value's own `toString` method
// and avoid being faked out.
? // jshint ignore:line
reNative.test(fnToString.call(value))
// Fallback to a host object check because some environments will represent
// things like typed arrays as DOM methods which may not conform to the
// normal native pattern.
:
(value && type == 'object' && reHostCtor.test(toString.call(value))) || false;
}
function isPrimitive(obj){
if(typeof obj == "undefined" || obj === null)
return true;
if(typeof obj == "object" || typeof obj == "function")
return false;
return true;
}
function hasJQuery(){
if(typeof $ == "function" && typeof $.prototype == "object" && typeof $.fn != "undefined")
return $;
if(typeof jQuery == "function" && typeof jQuery.prototype == "object" && typeof jQuery.fn != "undefined")
return jQuery;
return false;
}
function isJQuery(obj){
if(!obj || typeof obj != "object")
return false;
var jq = hasJQuery();
if(!jq)
return false;
return obj instanceof jq;
}
function jqExtend(jQ=false){
if(!jQ)
jQ = hasJQuery();
if(jQ){
var fn = jQ.fn;
fn.selectText = function(){
var doc = document;
for(var i = 0; i<this.length; i++){
var element = this[i];
var range;
if (doc.body.createTextRange){
range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
} else if (window.getSelection){
var selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
}
};
fn.fare = function(){
$(this).fadeOut(function(){
$(this).remove();
});
};
fn.textOnly = function(trim=true){
var c = this.clone();
c.children().remove();
if(trim)
return c.text().trim();
return c.text();
};
}
// else
// console.log("no jQuery, no extensions :(");
}
jqExtend();
class BPLogger {
constructor(name = false, prefix = false, debugging = false, level = 1){
if(!name && typeof GM_info !== "undefined")
name = GM_info.script.name;
if(prefix === false && name)
prefix = name;
if(!name)
name = "Logger";
this.name = name;
this.prefix = prefix;
this.debugging = debugging;
this.level = level;
this.colors = {
default: [180, 100],
warn: [60, 100],
error: [0, 100],
success: [150, 100]
};
this.history = [];
this.keepHistory = false;
if (typeof name == "object"){
Object.assign(this, name);
}
return this;
}
writeLog(args, type = "default", level = 1) {
if (this.keepHistory)
this.history.push([Date.now(), type, level, args]);
if (level > this.level)
return;
if(this.prefix)
args = ["%c" + this.prefix + ":", `color: hsl(${this.colors[type][0]},${this.colors[type][1]}%,80%); background-color: hsl(${this.colors[type][0]},${this.colors[type][1]}%,15%); font-weight: 900!important`, ...args];
if (this.debugging)
args = [...args, new Error().stack.replace(/^\s*(Error|Stack trace):?\n/gi, "").replace(/^([^\n]*\n)/, "\n")];
if(["warn", "error"].includes(type))
console[type](...args);
else
console.log(...args);
}
log(...args){
this.writeLog(args);
}
warn(...args){
this.writeLog(args, "warn");
}
error(...args){
this.writeLog(args, "error");
}
success(...args){
this.writeLog(args, "success");
}
}
function BPLogger_default(...args){
if(args.length<=0)
args = "";
var logger = new BPLogger(args);
log = function(...args){
logger.log(...args);
};
warn = function(...args){
logger.warn(...args);
};
error = function(...args){
logger.error(...args);
};
success = function(...args){
logger.success(...args);
};
return logger;
}
String.prototype.matches = function(rex){
if(!(rex instanceof RegExp))
return log("Not a regular Expression:", rex);
return rex.exec(this);
};
function mergeDeep(target, source, mutate=true){
let output = mutate ? target : Object.assign({}, target);
if(typeof target == "object"){
if(typeof source != "object")
source = {source};
Object.keys(source).forEach(key => {
if(typeof source[key] == "object"){
if(!(key in target))
Object.assign(output, { [key]: source[key] });
else
output[key] = mergeDeep(target[key], source[key]);
}else{
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
function bpModal(style="light"){
var r = {};
r.messages = [];
r.count = 0;
const styles = {
light: {
colors: {
msg: {
border: "#666",
background: "#eee",
color: "#333"
},
error: {
border: "#a10",
background: "#ffcabf",
color: "#610"
},
warn: {
border: "#cc4",
background: "#fec",
color: "#880"
},
success: {
border: "#190",
background: "#bf9",
color: "#070"
},
modal: {
border: "#fff",
background: "#eee",
color: "#333"
},
modalClose: {
border: "#fff",
background: "#000",
color: "#fff"
}
}
},
dark: {
colors: {
msg: {
border: "#aaa",
background: "#222",
color: "#ddd"
},
error: {
border: "#722",
background: "#300",
color: "#c66"
},
warn: {
border: "#cc4",
background: "#330",
color: "#dd6"
},
success: {
border: "#190",
background: "#040",
color: "#6d6"
},
modal: {
border: "#333",
background: "#000",
color: "#ccc"
},
modalClose: {
border: "#fff",
background: "#000",
color: "#fff"
}
}
},
};
r.style = styles[style];
if(!r.style)
r.style = styles.light;
r.css = `#messageoverlays{
position:fixed;
top:20vh;
z-index:1000;
text-align:left;
margin: 0 auto;
left:50%;
transform: translate(-50%, 0px);
}
#messageoverlays>.table{
margin: 0px auto;
}
#messageoverlays .msg{
display:inline-block;
width:auto;
margin: 5px auto;
position:relative;
padding: 10px;
box-sizing: border-box;
border-radius: 5px;
}
#messageoverlays .msg{
border: 1px solid ${r.style.colors.msg.border};
background-color: ${r.style.colors.msg.background};
color: ${r.style.colors.msg.color};
}
#messageoverlays .msg.error{
border: 1px solid ${r.style.colors.error.border};
background-color: ${r.style.colors.error.background};
color: ${r.style.colors.error.color};
}
#messageoverlays .msg.warn{
border: 1px solid ${r.style.colors.warn.border};
background-color: ${r.style.colors.warn.background};
color: ${r.style.colors.warn.color};
}
#messageoverlays .msg.success{
border: 1px solid ${r.style.colors.success.border};
background-color: ${r.style.colors.success.background};
color: ${r.style.colors.success.color};
}
.closebutton{
font-weight: 900;
font-size: 12px;
cursor: pointer;
z-index: 20;
opacity: 0.75;
color: #fff;
background-color: #a10;
padding: 0 5px 1px;
border-radius: 100%;
position: absolute;
right: -5px;
top: -2px;
line-height: 16px;
}
.closebutton:hover,
.bpModback .modclose:hover{
opacity: 1;
}
.bpModback{
position:fixed;
width:100%;
height:100%;
display:table;
left:0;
top:0;
z-index:99000;
}
.bpModback.tint{
background-color:rgba(0,0,0,0.5);
}
.bpModback.nomodal{
display:block;
width: auto;
height: auto;
left: 50%;
top: 20px;
transform: translateX(-50%);
}
.bpModback .modcent{
display:table-cell;
vertical-align:middle;
height:100%;
max-height:100%;
min-height:100%;
}
.bpModback.nomodal .modcent{
height:auto;
max-height:auto;
}
.bpModback .modtable{
display:table;
margin:auto;
position:relative;
left:0;
}
.bpModback .modframe{
border-radius: 6px;
border:10px solid ${r.style.colors.modal.border};
display:block;
background-color: ${r.style.colors.modal.background};
box-shadow: 0 0 20px rgba(0,0,0,0.5);
max-height: 90vh!important;
max-width: 90vw!important;
overflow-y:auto;
}
.bpModback .modclose{
display:block;
background-color: ${r.style.colors.modalClose.background};
color: ${r.style.colors.modalClose.color};
opacity:0.7;
position:absolute;
right:-12px;
top:-12px;
-webkit-border-radius: 20px;
-moz-border-radius: 20px;
border-radius: 20px;
border:4px solid ${r.style.colors.modalClose.border};
font-weight:900;
font-size:12pt;
padding:0px 7px;
cursor:pointer;
z-index:400;
}
.bpModback .modbox{
position:relative;
display: table;
padding:20px 20px 10px;
color: ${r.style.colors.modal.color};
overflow: unset;
display:block;
text-align: left;
}
.bpModback .table{
display:table;
}
.bpModback .tr{
display:table-row;
}
.bpModback .td{
display: table-cell;
}
#watch #player{
height: 200px;
}`;
r.setup = function(){
// TODO: retrofill
if(typeof $ !== "function"){
return console.error("bpMenu: No jQuery '$'; can't continue");
// console.log("jQuery not available?\nTrying to insert & load...", typeof $);
// script = document.createElement("script");
// script.type = "text/javascript";
// script.onload = function(){
// r.setup();
// };
// script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js";
// head.appendChild(script);
// return;
}
// console.log("Actual setup with jQuery");
if($("head #bpModalStyle").length<=0){
$("head").append(`<style id="bpModalStyle">
${r.css}
</style>`);
}
};
r.msg = function(instr, id="", modal=true, callback=null){
r.count++;
if(!id){
id = "bpmod" + r.count;
}
var noclose = false;
if(typeof(modal)=="string" && modal == "noclose"){
noclose = true;
modal = true;
}
var m = {
id : id,
msg: instr,
callback: callback,
modal: modal,
obj: $("<div class='bpModback " + (modal?"tint":"nomodal") + (noclose?" noclose":"") + "' id='" + id + "'><div class='tr'><div class='modcent'><div class='modtable'><div class='modclose'>X</div><div class='modframe'><div class='modbox'>" + instr + "</div></div></div></div></div></div>"),
close: function(){
this.obj.remove();
delete r.messages[this.id];
if(this.callback)
this.callback(this);
}
};
$("body").append(m.obj);
$("#" + id + ":not('.noclose') .modcent").click(function(e){
if(e.target == this)
m.close(this);
});
$("#" + id + " .modclose").click(function(e){
m.close(this);
});
r.messages[id] = m;
return m;
};
r.close = function(el="all"){
if(el=="all"){
}
};
r.setup();
return r;
}
//if("undefined" === typeof bpModHelper){ // jshint ignore:line
// var bpModHelper = bpModal(); // jshint ignore:line
//}
function message(content, classname, id, expirein, closable){
expirein = typeof expirein !== 'undefined' ? expirein : 0;
if(closable===undefined)
closable = true;
var expires = expirein !== 0 ? true : false;
if (id === undefined || id === ""){
for (var i = 0; i < 512; i++){
if ($(document).find("#message-" + i)[0] !== undefined){} else {
this.id = "message-" + i;
break;
}
}
} else {
this.id = id;
}
var fid = this.id;
this.expire = function(){
if (expirein > 0){
if(this.extimer)
window.clearTimeout(this.extimer);
this.extimer = window.setTimeout(function(){
$("#" + fid).fadeOut(function(){
$("#" + fid).remove();
});
}, expirein);
}
};
this.html = "<div id='" + this.id + "' class='table'><div class='msg " + classname + "'>" + content + (closable?"<div class='closebutton' id='c-" + this.id + "'>x</div>":"") + "</div></div>";
}
function overlaymessage(content, classname, id, expirein, closable){
expirein = typeof expirein !== 'undefined' ? expirein : 5000;
classname = classname || "hint";
id = id || "";
var curmes = new message(content, classname, id, expirein, closable);
//console.log(curmes);
if($("#messageoverlays").length<=0)
$("body").append("<div id='messageoverlays'></div>");
$("#messageoverlays").append(curmes.html);
$(".msg .closebutton").off("click").on("click", function(){
console.log("close", $(this).parent().parent());
$(this).parent().parent().fare();
});
curmes.expire();
}
function msg(content, classname, id, expirein){
overlaymessage(content, classname, id, expirein);
}
function msgbox(content, classname="", id=""){
if (id === undefined || id === ""){
for (var i = 0; i < 512; i++){
if ($(document).find("#message-" + i)[0] !== undefined){} else {
id = "message-" + i;
break;
}
}
}
return "<div id='" + id + "' class='msg " + classname + "'>" + content + "</div>";
}
String.prototype.rIndexOf = function(regex, startpos) {
var indexOf = this.substring(startpos || 0).search(regex);
return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
};
var regEsc = regEsc ? regEsc : function(str) {
if (typeof str != "string") {
console.warn("called regEsc with non-string:", str);
return str;
}
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
};
String.prototype.replaceAllInsensitive = function(str1, str2)
{
return this.replace(new RegExp(regEsc(str1), "gi"),(typeof str2 == "string")?str2.replace(/\$/g,"$$$$"):str2);
};
function scrollIntoView(object, offsetTop = 20, offsetLeft = 20){
object = $(object);
if(object.length<=0)
return;
object = object[0];
var offset = $(object).offset();
$('html, body').animate({
scrollTop: offset.top - offsetTop,
scrollLeft: offset.left - offsetLeft
});
object.scrollIntoView();
}
class elementSelector{
constructor(element=false, attributes=true){
this.tagName =
this.id =
this.className =
this.sibling =
"";
this._attribs = {};
if(element){
if(isJQuery(element)){
if(element.length<=0)
return;
element = element[0];
}
if(element instanceof HTMLElement){
var t = this;
this.element = element;
this.tagName = element.tagName.toLowerCase();
if(element.id)
this.id = "#" + element.id;
if(element.className){
this.className = "." + element.className.trim().replace(/\s+/g, ".");
}
if(attributes)
for(let attr of element.attributes){
if(["id", "class"].includes(attr.name))
continue;
if(typeof attributes == "string")
attributes = [attributes];
if(attributes instanceof Array && !attributes.includes(attr.name))
continue;
if(attr.value)
this._attribs[attr.name] = attr.value;
}
var p = element.parentElement;
if(p && p.childElementCount>1){
var sibs = p.querySelectorAll(":scope > " + this.tagName);
if(sibs && sibs.length>1){
for(let i = 0; i<sibs.length; i++){
let sib = sibs[i];
if(sib == element){
// console.log("found in siblings:", el);
if(i==0)
t.sibling = ":first-of-type";
else if(i==sibs.length-1)
t.sibling = ":last-of-type";
else
t.sibling = ":nth-of-type(" + (i+1) + ")";
return false;
}
// console.log("not same:", sib, el);
}
}
}
}
}
}
attribs(filter=false){
var r = "";
if(typeof filter == "string")
filter = [filter];
for(let k in this._attribs){
if(filter instanceof Array && !filter.includes(k))
continue;
let v = this._attribs[k];
r += `[${k}="${v}"]`;
}
return r;
}
get attributes(){
return this.attribs();
}
clone(){
var r = new elementSelector();
Object.assign(r, this);
r._attribs = {};
for(let k in this._attribs)
r._attribs[k] = this._attribs[k];
return r;
}
toString(includeAttribs = true, includeSibling = true){
return this.tagName + this.id + this.className + (includeAttribs?this.attribs(includeAttribs):"") + (includeSibling?this.sibling:"");
}
countMatches(descendants = null, includeAttribs = true, includeSibling = true){
if(descendants)
return descendants.add(this, true).toString(includeAttribs, includeSibling);
var s = this.toString(includeAttribs, includeSibling);
if(s)
return document.querySelectorAll(s).length;
return 0;
}
}
class elementSelectorChain{
constructor(elements = false, reverse = false, attributes=true){
this.selectors = [];
if(elements){
if(isJQuery(elements))
elements = elements.get();
if(elements instanceof Array){
if(reverse)
elements = elements.reverse();
for(let e of elements){
if(!e){
this.add(null);
continue;
}
if(e instanceof elementSelector)
this.add(e);
else
this.add(new elementSelector(e, attributes));
}
}
else if(elements instanceof HTMLElement)
this.add(new elementSelector(elements, attributes));
else console.warn("elementSelectorChain: Could not construct with", elements, ", starting empty");
}
}
add(selector, toStart=false){
if(toStart){
this.selectors = [selector, ...this.selectors];
return this;
}
this.selectors.push(selector);
return this;
}
each(func, reverse=false){
var l = this.selectors.length-1;
for(let i = 0; i<=l; i++){
let j = reverse?l-i:i;
let e = this.selectors[j];
let r = func(e, j);
if(r===false)
break;
if(r === null || r instanceof elementSelector)
this.selectors[j] = r;
}
}
length(includeEmpty=false){
if(includeEmpty)
return this.selectors.length;
var c = 0;
for(let s of this.selectors)
if(s) c++;
return c;
}
get(ix){
return this.selectors[ix];
}
getElements(){
var r = [];
var s = this.toString();
if(s)
r = document.querySelectorAll(s);
return r;
}
clone(){
var r = new elementSelectorChain();
this.each(function(s){
if(!s)
r.add(null);
else
r.add(s.clone());
});
return r;
}
toString(includeAttribs = true, includeSibling = true){
var r = "";
var isDirect = true;
var isFirst = true;
for(let e of this.selectors){
if(!e){
isDirect = false;
continue;
}
r += (isFirst?"":(isDirect?" > ":" ")) + e.toString(includeAttribs, includeSibling);
isDirect = true;
isFirst = false;
}
return r;
}
countMatches(includeAttribs = true, includeSibling = true){
var s = this.toString(includeAttribs, includeSibling);
if(s)
return document.querySelectorAll(s).length;
return 0;
}
}
/**
* @name getSelector
* @description Get a matching CSS selector for the given HTML Element
* @param {(HTMLElement|jQuery)} element - The object (instance of HTMLElement or jQuery/$) for which to compose the selector
* @param {boolean} [minimal=true] - Try to trim selectors that don't narrow down the search on this page? (default: true)
* @param {(boolean|string|string[]|"auto")} [attributes="auto"] - Should attributes be included? (default: "auto")
* - string/string[]: Filter by attribute name[list]
* - \"auto\": include if it narrows down the search
* @param {boolean} [preferTopDown=true] - Try to trim unnecessary selectors closest to the element. Otherwise, trim unnecessary selectors closest to the document root. Only significant when minimal==true (default: true)
* @returns {string} CSS Selector
*/
function getSelector(element, minimal = true, attributes = "auto", preferTopDown = true){
return traverseAncestryForSelectors(element, minimal, attributes, preferTopDown).toString();
}
function getSelectors(elements, minimal = true, attributes = "auto", preferTopDown = true){
var r = [];
for(let element of elements)
r.push(traverseAncestryForSelectors(element, minimal, attributes, preferTopDown).toString());
return r;
}
function getSelectorChoices(element, attributes = "auto"){
var r = [];
r.push(traverseAncestryForSelectors(element, true, attributes, true).toString());
r.push(traverseAncestryForSelectors(element, true, attributes, false).toString());
r.push(traverseAncestryForSelectors(element, false, attributes).toString());
return r;
}
function traverseAncestryForSelectors(element, minimal = true, attributes = "auto", preferTopDown = true){
if(!element)
throw new Error("traverseAncestryForSelectors: Not a valid element");
if(isJQuery(element)){
if(element.length<=0)
throw new Error("traverseAncestryForSelectors: No element in jQuery object");
element = element[0];
}
var ancestry = [];
if(typeof $ == "function" && typeof $.prototype == "object" && typeof $(element) == "object" && typeof $(element).parents == "function"){
ancestry = $(element).add($(element).parents());
}
else{
var p = element;
while(p && p != document){
ancestry.push(p);
p = p.parentElement;
}
ancestry = ancestry.reverse();
}
var fullChain = new elementSelectorChain(ancestry, false, attributes);
var tmpChain = fullChain.clone();
var chain = new elementSelectorChain();
var cs = fullChain.toString(attributes);
if(!cs)
throw new Error("Selector empty");
var bestCount = document.querySelectorAll(cs).length;
if(bestCount<=0){
console.warn("Selector chain too restrictive/broken?", cs, fullChain);
}
// console.log("Complete chain:", cs, fullChain, bestCount==1?"UNIQUE":bestCount);
function applyIfViable(tmpSelector, ix){
if(fullChain.selectors[ix] === null || fullChain.selectors[ix] == tmpSelector)
return fullChain.selectors[ix];
if(tmpSelector && (tmpSelector.tagName + tmpSelector.id + tmpSelector.className) == "")
return fullChain.selectors[ix];
tmpChain.selectors[ix] = tmpSelector;
let els = tmpChain.getElements();
// console.log(els.length, bestCount, els.is(element), els, element);
if(els.length>bestCount || els[0] != element){
tmpChain.selectors[ix] = fullChain.selectors[ix].clone();
}
else{
if(tmpSelector)
fullChain.selectors[ix] = tmpSelector.clone();
else
fullChain.selectors[ix] = null;
// console.log("set to ", tmpSelector, fullChain.selectors[ix]);
}
return fullChain.selectors[ix];
}
// console.log("going DOWN");
fullChain.each(function(sel, ix){
// console.log(ix, sel.toString(), tmpChain.selectors[ix]);
if(!sel || !sel.sibling)
return;
var c = sel.clone();
c.sibling = "";
// console.log("before sib check", ix, c, tmpChain.selectors[ix]);
applyIfViable(c, ix);
// console.log("after sib check", tmpChain.selectors[ix]);
});
// console.log("going UP", fullChain.toString());
if(!minimal)
return fullChain;
fullChain.each(function(sel, ix){
// console.log(ix, sel.toString());
var c = sel;
if(minimal && ix<=fullChain.length()-1)
c = applyIfViable(null, ix);
if(c){
c = c.clone();
if(attributes=="auto")
for(let k in c._attribs){
// console.log("before attrib remove", k, c._attribs);
delete c._attribs[k];
// console.log("before attrib check", k, c._attribs);
c = applyIfViable(c, ix).clone();
// console.log("after attrib check", k, c._attribs);
}
for(let k of ["tagName", "className"]){
// console.log("before attrib remove", ix, k, c);
c[k] = "";
// console.log("before attrib check", k, c);
c = applyIfViable(c, ix).clone();
// console.log("after attrib check", k, c);
}
}
else{
// console.log("skipped", sel.toString());
}
chain.add(c, preferTopDown);
//console.log("currently at", s);
if(minimal && c && document.querySelector(chain.toString()) == element)
return false;
}, preferTopDown);
return chain;
}
// TODO: option to filter attribs by specific values also, not just keys
/* globals isElement, isNativeFunction, uneval */
function stringify(obj, forHTML, onlyOwnProperties, completeFunctions, level, maxLevel, skipEmpty){
if(!level) level = 0;
var r = "";
if(obj===undefined) r = "[undefined]";
else if(obj === null) r = "[null]";
else if(obj === false) r = "FALSE";
else if(obj === true) r = "TRUE";
else if(obj==="") r = "[empty]";
else if(typeof obj == "object"){
var isDOMElement = isElement(obj);
if(onlyOwnProperties === undefined) onlyOwnProperties = true;
if(completeFunctions === undefined) completeFunctions = false;
if(maxLevel === undefined) maxLevel = 5;
if(skipEmpty === undefined) skipEmpty = false;
r = "[object] ";
var level_padding = "";
var padString = " ";
for(var j = 0; j < level; j++) level_padding += padString;
if(isDOMElement){
r = "[DOMElement " + obj.nodeName + "] ";
skipEmpty = true;
completeFunctions = false;
}
if(level<maxLevel){
r += "{\n";
if(isDOMElement){
r += level_padding + padString + "HTML => " + obj.outerHTML.replace(/\r?\n/g, "\\n").replace(/\s+/g, " ") + "\n";
}
for(var item in obj){
try{
var value = obj[item];
if(onlyOwnProperties && obj.hasOwnProperty && !obj.hasOwnProperty(item) || isNativeFunction(value) || skipEmpty && (value===undefined || value === null || value===""))
continue;
if(typeof(value) == 'object'){
r += level_padding + padString + "'" + item + "' => ";
r += stringify(value, forHTML, onlyOwnProperties, completeFunctions, level+1, maxLevel, skipEmpty) + "\n";
}else if(typeof(value) == 'undefined'){
r += level_padding + padString + "'" + item + "' => [undefined]\n";
}else{
if(typeof(value.toString)=="function")
value = value.toString();
if(!completeFunctions){
let m = value.match(/function\s*\(([^\)]*)\)\s*\{/i);
if(m)
value = "function(" + m[1] + ")";
}
r += level_padding + padString + "'" + item + ("' => \"" + value).replace(/\r?\n/g, "\\n").replace(/\s+/g, " ") + "\"\n";
}
}catch(e){
console.log(e);
}
}
r += level_padding + "}";
}else
r += "[Max depth of " + maxLevel + " exceeded]";
}
else if(typeof obj == "function"){
if(typeof(obj.toString)=="function")
r = obj.toString();
else
r = uneval(obj);
if(!completeFunctions){
let m = r.match(/function\s*\(([^\)]*)\)\s*\{/i);
if(m)
r = "function(" + m[1] + ")";
}
}
else
r = obj + "";
if(level===0){
if(!!forHTML){
r = r.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
r = "<pre>" + r + "</pre>";
}
}
return r;
}
function isNode(o){
return (
typeof Node === "object" ? o instanceof Node :
o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
);
}
/* globals HTMLDocument */
function isElement(o){
return (
((typeof HTMLElement === "object" && o instanceof HTMLElement) || (typeof Element === "object" && o instanceof Element) || (typeof HTMLDocument === "object" && o instanceof HTMLDocument))? true : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
);
}
function stringifyWithFuncs(val, withTabs = true){
return JSON.stringify(val, function(key, value){
if (typeof value === 'function') {
return value.toString();
}
return value;
}, withTabs?"\t":" ");
}
function toText(str, maxBreaks=2){
// if($ === undefined){
// $ = jQuery = require( "jquery" )(new JSDOM("").window);
// }
var hack = "__break god dammit__";
str = str.replace(/(<(?:br|hr) ?\/?>|<\/(?:p|li|div|td|h\d)>)/gi, hack + "$1");
str = $("<div/>").append(str).text();
var rex = new RegExp(hack, "gi");
str = str.replace(rex, "\n");
rex = new RegExp("(\\n{" + maxBreaks + "})\\n+", "g");
str = str.replace(rex, "$1");
return str.trim();
}
function toVarName(str){
return str
.replace(/\s+(\w)/g, function(_, a){
return a.toUpperCase();
})
.replace(/[^a-zA-Z0-9]/g, "_")
.replace(/^([0-9])/, "_$1");
}
function trim(s, what="\\s"){
var rex = new RegExp("^(?:[" + what + "])*((?:[\\r\\n]|.)*?)(?:[" + what + "])*$");
var m = s.match(rex);
// log(m);
if(m !== null && m.length>=2)
return m[1];
return "";
}
function varToPretty(str){
return str.replace(/(.+?)([A-Z])/g, "$1 $2").replace(/_|-/g, " ").replace(/\s\s+/g, " ").replace(/\b([a-z])/g, function(v,i){return v.toUpperCase();});
}
class eleWaiter{
constructor(sel, cb, cbFail=null, findIn=null, delay=500, maxTries=50, alwaysOn=false, autoStart=true, debug = false, logFunc = null){
this.sel = "";
this.cb = null;
this.cbFail = null;
this.findIn = null;
this.delay = 500;
this.maxTries = 50;
this.alwaysOn = false;
this.autoStart = true;
this.debug = false;
this.logFunc = null;
this.__running = false;
this.__tries = 0;
this.__timer = 0;
this.__jqo = {};
if(typeof sel == "object" && !(sel instanceof Array)){ // 2022-04-16 : Now allowing array of selectors
// log("got object");
Object.assign(this, sel);
}
else{
this.sel = sel;
this.cb = cb;
if(cbFail!== undefined || cbFail!== null)
this.cbFail = cbFail;
if(findIn)
this.findIn = findIn;
this.delay = delay;
this.maxTries = maxTries;
this.alwaysOn = alwaysOn;
this.autoStart = autoStart;
this.debug = debug;
this.logFunc = logFunc;
}
if(!(this.sel instanceof Array)){ // 2022-04-16 : Now allowing array of selectors
this.sel = [this.sel];
}
if(this.debug){
if(typeof this.debug == "string"){
this.debug = {
prefix: this.debug,
level: 1
};
}
else if(typeof this.debug == "number"){
this.debug = {
prefix: "",
level: this.debug
};
}
else if(typeof this.debug == "object"){
if(!this.debug.prefix)
this.debug.prefix = "";
if(!this.debug.level)
this.debug.level = 1;
}
else{
this.debug = {
prefix: "",
level: 1
};
}
}
if(!this.logFunc){
var prefix = "";
if(this.debug)
prefix = this.debug.prefix;
if(typeof BPLogger != "undefined"){
var logger = new BPLogger(prefix ? prefix + " EleWaiter" : "EleWaiter");
this.logFunc = logger.log.bind(logger);
// this.debug.prefix = false;
}
else{
this.logFunc = function(...args){
console.log("EleWaiter:", ...args);
};
}
}
this.log(this, 3);
if(this.autoStart)
this.__wait();
}
log(...args){
if(!this.debug)
return;
if(typeof args == "object" && args instanceof Array && args.length>=2 && typeof args[args.length-1] == "number"){
var level = args[args.length-1];
if(level>this.debug.level){
return;
}
args.pop();
}
this.logFunc(...args);
}
start(){
if(!this.__running){
this.log("Start waiting", this.findIn, this.sel, 1);
this.__wait();
}
}
stop(){
clearTimeout(this.__timer);
this.__running = false;
}
__wait(){
if(!this.findIn || this.findIn == "document"){
if(!!document)
this.findIn = document;
else
this.findIn = $(":root");
}
this.__running = true;
if(this.maxTries!=-1)
this.__tries++;
var triesLeft = this.alwaysOn?1:(this.maxTries - this.__tries);
this.log("tries left:", triesLeft, 3);
this.__jqo = $();
for(let sel of this.sel){
if(typeof sel == "function"){ // 2022-07-11: predicate style
this.log("sel is func:", this.sel, 3);
jqo = $(this.findIn);
var res = sel(jqo);
if(!res){
if(!this.alwaysOn)
this.log("Not true:", sel.toString(), "for", this.findIn, 3);
if(triesLeft!==0){
this.__timer = setTimeout(function(){this.__wait();}.bind(this), this.delay);
if(this.alwaysOn)
this.__result(false);
}
else
this.__result(false);
return;
}
else{
this.__jqo = this.__jqo.add(res);
this.log("Found something, is now:", this.__jqo, 3);
}
continue;
}
var jqo = $(this.findIn).find(sel);
if(jqo.length<=0){
if(!this.alwaysOn)
this.log("Not found: " + sel, "in", this.findIn, 3);
if(triesLeft!==0){
this.__timer = setTimeout(function(){this.__wait();}.bind(this), this.delay);
if(this.alwaysOn)
this.__result(false);
}
else
this.__result(false);
return;
}
else{
this.__jqo = this.__jqo.add(jqo);
this.log("Found something, is now:", this.__jqo, 3);
}
}
this.__result(this.__jqo);
if(this.alwaysOn){
this.log("Always on, repeat", 3);
this.__timer = setTimeout(function(){this.__wait();}.bind(this), this.delay);
}
}
__result(success=false){
if(!this.alwaysOn){
this.__running = false;
this.log("Result:", success, 2);
}else if(this.debug.level>2)
this.log("Result:", success, 1);
if(success){
if(this.cb!==undefined && typeof this.cb == "function")
this.cb(this.__jqo);
else
console.log("Warning: callback cb not function", this.cb);
}
else{
if(this.cbFail!==undefined && typeof this.cbFail == "function")
this.cbFail(this.__jqo);
}
}
}
if("undefined" === typeof eleWaiters){ // jshint ignore:line
var eleWaiters ={}; // jshint ignore:line
}
function waitFor(sel, cb, cbFail=null, findIn="document", delay=500, maxTries=50, alwaysOn=false, debug = false){ // 2021-01-29
return new eleWaiter(sel, cb, cbFail, findIn, delay, maxTries, alwaysOn, true, debug);
}