您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Small script to be @require-d, providing useful functions and extensions I like to regularly refer to
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/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);
- }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址