BP Funcs

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

  1. // ==UserScript==
  2. // @name BP Funcs
  3. // @description Small script to be @require-d, providing useful functions and extensions I like to regularly refer to
  4. // @version 1.1.0
  5. // @namespace BP
  6. // @author Benjamin Philipp <dev [at - please don't spam] benjamin-philipp.com>
  7. // ==/UserScript==
  8.  
  9. /*
  10. BP Funcs, as of 2023-06-02 17:22:41 (GMT +02:00)
  11. */
  12.  
  13. class SelectorDef{
  14. constructor(selectors="", refineFunc=null, within=null){
  15. this.selectors = [];
  16. if(selectors){
  17. switch(typeof selectors){
  18. case "object":
  19. if(selectors instanceof Array){
  20. this.selectors = [...selectors];
  21. }
  22. else{
  23. try{
  24. Object.assign(this, selectors);
  25. return this;
  26. }catch(e){
  27. console.warn("Could not assign Object to SelectorDef with", selectors);
  28. }
  29. }
  30. break;
  31. case "string":
  32. this.selectors = [selectors];
  33. break;
  34. default:
  35. console.warn("Could not construct SelectorDef with selector of type '" + (typeof selectors) + "':", selectors);
  36. }
  37. }
  38. this.refineFunc = refineFunc;
  39. this.within = within;
  40. return this;
  41. }
  42. add(selectors="", refineFunc=null, within=null){
  43. this.selectors.push(new SelectorDef(selectors, refineFunc, within));
  44. return this;
  45. }
  46. find(within=null, separate=false){
  47. if(!within)
  48. within = document;
  49. var r;
  50. if(separate)
  51. r = { length: 0, hasEMpty: false };
  52. else
  53. r = $();
  54. var i = 0;
  55. for(let sel of this.selectors){
  56. let f = null;
  57. if(sel instanceof SelectorDef){
  58. f = sel.find(within);
  59. }
  60. else{
  61. f = $(within).find(sel);
  62. if(this.refineFunc)
  63. f = this.refineFunc(f);
  64. }
  65. if(separate){
  66. if(f && f.length)
  67. r[i] = f;
  68. else{
  69. r[i] = null;
  70. r.hasEMpty = true;
  71. }
  72. r.length++;
  73. }
  74. else if(f && f.length)
  75. r = r.add(f);
  76. i++;
  77. }
  78. return r;
  79. }
  80. get(separate = false){
  81. return this.find(this.within, separate);
  82. }
  83. waitFor(cb, stopForAny = false, requireAll = false, cbFail=null, doneClass="", interval=200, maxTries=50){
  84. console.log("waiting, maxTries = " + maxTries);
  85. var res = this.get(requireAll);
  86. var runAgain = true;
  87. if(res.length>0){
  88. runAgain = maxTries <= -1 && !stopForAny;
  89. if(requireAll){
  90. if(!res.hasEMpty){
  91. var results = res[0];
  92. console.log("got all?", res);
  93. for(let i = 1; i<res.length; i++)
  94. results = results.add(res[i]);
  95. cb(results);
  96. }
  97. else
  98. runAgain = true;
  99. }
  100. else
  101. cb(res);
  102. }
  103. else
  104. runAgain = true;
  105. if(runAgain){
  106. var t = this;
  107. if(maxTries>0){
  108. maxTries--;
  109. } else if(maxTries==0){
  110. if(typeof cbFail == "function")
  111. cbFail();
  112. return;
  113. }
  114. this.__timer = setTimeout(function(){
  115. t.waitFor(cb, stopForAny, requireAll, cbFail, doneClass, interval, maxTries);
  116. }, interval);
  117. }
  118. }
  119. stop(){
  120. if(this.__timer)
  121. clearTimeout(this.__timer);
  122. }
  123. }
  124. String.prototype.after = function(str, fromRight, returnAll){
  125. if(fromRight === undefined)
  126. fromRight = false;
  127. if(returnAll === undefined)
  128. returnAll = false;
  129. var os = this.indexOf(str);
  130. if(fromRight)
  131. os = this.lastIndexOf(str);
  132. if(os<0)
  133. return returnAll?this:"";
  134. return this.substring(os + str.length);
  135. };
  136. function arraySortRelevance(arr, q, selectRelevantProperty = (item)=>item){
  137. q = q.toLowerCase().replace(/&/g, " and ");
  138. var words = {
  139. q: q.split(/[\b\s./\\'-]/)
  140. };
  141. var matchFunc = function(a, b){
  142. return a.localeCompare(b, undefined, {sensitivity: "accent", usage: "sort" })===0;
  143. };
  144. arr.sort(function(itemA, itemB){
  145. var items = {
  146. a: selectRelevantProperty(itemA).toLowerCase().replace(/&/g, " and "),
  147. b: selectRelevantProperty(itemB).toLowerCase().replace(/&/g, " and ")
  148. };
  149. if(matchFunc(items.a, items.b)){
  150. return 1;
  151. }
  152.  
  153. if(matchFunc(items.a, q))
  154. return -1;
  155. if(matchFunc(items.b, q))
  156. return 1;
  157.  
  158. var score = {
  159. a: 0,
  160. b: 0,
  161. last: {
  162. a: 0,
  163. b: 0
  164. }
  165. };
  166. var exactMult = 2;
  167. var runMult = 2;
  168. var missingMult = 1;
  169. words.a = items.a.split(/[\b\s./\\'-]/);
  170. words.b = items.b.split(/[\b\s./\\'-]/);
  171. for(let k of["a", "b"]){
  172. for(let w of words.q){
  173. if(w.trim()==="")
  174. continue;
  175. missingMult = 1;
  176. var mi = items[k].indexOf(w);
  177. if(mi>=0){
  178. var s = w.length;
  179. var ni = mi + s;
  180. var prev = items[k].substring(mi-1, mi);
  181. var next = items[k].substring(ni, ni+1);
  182. if(! /\w/.test(prev + next)){
  183. s *= exactMult;
  184. }
  185. score[k] += score.last[k] + s;
  186. score.last[k] = w.length * (runMult - 1);
  187. }
  188. else{
  189. score[k] -= w.length * missingMult;
  190. score.last[k] = 0;
  191. }
  192. }
  193. missingMult = 0.5;
  194. for(let wk of words[k]){
  195. if(!q.indexOf(wk)>=0){
  196. score[k] -= wk.length * missingMult;
  197. }
  198. }
  199. }
  200. // log(score, items, q);
  201. if(score.a>score.b)
  202. return -1;
  203. if(score.a<score.b)
  204. return 1;
  205. if(items.a.length<items.b.length)
  206. return -1;
  207. else
  208. return 1;
  209. });
  210. return arr;
  211. }
  212. String.prototype.before = function(str, fromRight, returnAll){
  213. if(fromRight === undefined)
  214. fromRight = false;
  215. if(returnAll === undefined)
  216. returnAll = false;
  217. var os = this.indexOf(str);
  218. if(fromRight)
  219. os = this.lastIndexOf(str);
  220. if(os<0)
  221. return returnAll?this:"";
  222. return this.substr(0, os);
  223. };
  224. function bpMenu(style="light"){
  225. var r = {};
  226. r.obj = null;
  227. r.items = {};
  228. const styles = {
  229. light: {
  230. colors : {
  231. background : "#fff",
  232. color : "#333",
  233. item : "#222",
  234. itemHover : "#268",
  235. itemBack : "transparent",
  236. frame : "#eee",
  237. button:{
  238. background: "#bbb",
  239. color: "#333",
  240. background_hover: "#157",
  241. color_hover: "#fff",
  242. }
  243. },
  244. fontSize: "14px"
  245. },
  246. dark: {
  247. colors : {
  248. background : "#000",
  249. color : "#ccc",
  250. item : "#ddd",
  251. itemHover : "#6ce",
  252. itemBack : "transparent",
  253. frame : "#111",
  254. button:{
  255. background: "#444",
  256. color: "#ccc",
  257. background_hover: "#7cf",
  258. color_hover: "#000",
  259. }
  260. },
  261. fontSize: "14px"
  262. }
  263. };
  264. r.style = styles[style];
  265. if(!r.style)
  266. r.style = styles.light;
  267. r.css = `
  268. .bpbutton{
  269. padding: 5px;
  270. display: inline-block;
  271. background: ${r.style.colors.button.background};
  272. color: ${r.style.colors.button.color};
  273. cursor: pointer;
  274. font-weight: 600;
  275. line-height: 1em;
  276. }
  277. .bpbutton:hover, .bpbutton.on{
  278. background: ${r.style.colors.button.background_hover};
  279. color: ${r.style.colors.button.color_hover};
  280. }
  281. #bpMenu{
  282. position: fixed;
  283. z-index: 99999;
  284. top: -50px;
  285. right: 0px;
  286. height: 70px;
  287. display: inline-block;
  288. transition: top 0.5s;
  289. padding: 0px 0px 10px;
  290. }
  291. #bpMenu .inner{
  292. display: inline-block;
  293. background-color: ${r.style.colors.background};
  294. color: ${r.style.colors.color};
  295. padding: 0px 10px 5px;
  296. border-radius: 0 0 10px 10px;
  297. box-shadow: 0 0 10px rgba(0,0,0,0.5);
  298. }
  299. #bpMenu:hover{
  300. top: 0px;
  301. }
  302. #bpMenu .bp{
  303. display: inline-block;
  304. padding: 5px;
  305. font-size: ${r.style.fontSize};
  306. }
  307. #bpMenu .bp.item{
  308. color: ${r.style.colors.item};
  309. background: ${r.style.colors.itemBack};
  310. font-weight: bold;
  311. cursor: pointer;
  312. }
  313. #bpMenu .bp.item:hover{
  314. color: ${r.style.colors.itemHover};
  315. }
  316. #bpMenu .bp+.bp{
  317. margin-left: 10px;
  318. }
  319. `;
  320. r.setup = function(override=false){
  321. var head, script;
  322. head = document.getElementsByTagName("head")[0];
  323. if(typeof $ !== "function"){
  324. return console.error("bpMenu: No jQuery '$'; can't continue");
  325. // console.log("jQuery not available?\nTrying to insert & load...", typeof $);
  326. // script = document.createElement("script");
  327. // script.type = "text/javascript";
  328. // script.onload = function(){
  329. // r.setup();
  330. // };
  331. // script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js";
  332. // head.appendChild(script);
  333. // return;
  334. }
  335. if(typeof bpModal !== "function"){
  336. return console.error("bpMenu: No bpModal; can't continue");
  337. // console.warn("BP Modal not available?\nTrying to insert & load...", typeof bpModal);
  338. // script = document.createElement("script");
  339. // script.type = "text/javascript";
  340. // script.onload = function(){
  341. // r.setup();
  342. // };
  343. // script.src = "https://benjamin-philipp.com/js/gm/funcs.js?funcs=bpModal";
  344. // head.appendChild(script);
  345. // return;
  346. }
  347. // console.log("Setup BP Menu");
  348. if(override){
  349. $("body>#bpMenu").remove();
  350. }
  351. r.injectStyles(override);
  352.  
  353. if(!$("body>#bpMenu").length)
  354. $("body").append("<div id='bpMenu'><div class='inner'></div></div>");
  355. r.obj = $("body>#bpMenu");
  356. };
  357. r.injectStyles = function(override=false){
  358. if("undefined" != typeof GM_addStyle)
  359. return GM_addStyle(r.css);
  360. if(override)
  361. $("#bpMenuStyle").remove();
  362. if($("#bpMenuStyle").length<=0)
  363. $("head").append(`<style id="bpMenuStyle">${r.css}</style>`);
  364. };
  365. r.add = function(id, html, cb=null, title="", override=false, sel=""){
  366. let l = $("body>#bpMenu>.inner #" + id);
  367. let add = true;
  368. if(l.length >0){
  369. add = false;
  370. if(override){
  371. l.remove();
  372. add = true;
  373. }
  374. }
  375. if(add){
  376. if(title)
  377. title = " title='" + title + "'";
  378. $("body>#bpMenu .inner").append("<div id='" + id + "' class='bp" + (cb?" item":"") + "'" + title + ">" + html + "</div>");
  379. r.items[id] = $("#bpMenu #" + id);
  380. if(cb)
  381. $("#bpMenu #" + id).click(function(e){
  382. cb(e);
  383. });
  384. }
  385. };
  386. r.changeStyle = function(obj){
  387. mergeDeep(r.style, obj);
  388. r.injectStyles(true);
  389. };
  390. r.setup();
  391. return r;
  392. }
  393. //if("undefined" === typeof bpMenuHelper){ // jshint ignore:line
  394. // var bpMenuHelper = bpMenu(); // jshint ignore:line
  395. //}
  396. class BP_Color{
  397. constructor(colorOrRed, green, blue){
  398. this.color = {
  399. red: 0,
  400. green: 0,
  401. blue: 0
  402. };
  403. if(colorOrRed instanceof Color){
  404. this.color = colorOrRed.color;
  405. }
  406. else if(colorOrRed instanceof Array){
  407. this.color = {
  408. red: colorOrRed[0],
  409. green: colorOrRed[1],
  410. blue: colorOrRed[2]
  411. };
  412. }
  413. else if(typeof colorOrRed == "object"){
  414. this.color = {
  415. red: colorOrRed.red || colorOrRed.Red || colorOrRed.r || colorOrRed.R,
  416. green: colorOrRed.green || colorOrRed.Green || colorOrRed.g || colorOrRed.G,
  417. blue: colorOrRed.blue || colorOrRed.Blue || colorOrRed.b || colorOrRed.B
  418. };
  419. }
  420. else if(typeof colorOrRed == "string" && green === undefined){
  421. var hex = [];
  422. if(colorOrRed.startsWith("#")){
  423. colorOrRed = colorOrRed.replace(/^#?(\w)(\w)(\w)$/, "#$1$1$2$2$3$3");
  424. var c = Color.hexToRgb(colorOrRed);
  425. if(c){
  426. this.color.red = c[0];
  427. this.color.green = c[1];
  428. this.color.blue = c[2];
  429. }
  430. }
  431. }
  432. else{
  433. this.color.red = colorOrRed;
  434. this.color.green = green;
  435. this.color.blue = blue;
  436. }
  437. return this;
  438. }
  439. toRGB(){
  440. return [this.color.red, this.color.green, this.color.blue];
  441. }
  442. toHex(){
  443. return Color.rgbToHex(this.color.red, this.color.green, this.color.blue);
  444. }
  445. get(){
  446. return this.color;
  447. }
  448. static componentToHex(c) {
  449. var hex = c.toString(16);
  450. return hex.length == 1 ? "0" + hex : hex;
  451. }
  452. static rgbToHex(r, g, b) {
  453. return "#" + Color.componentToHex(r) + Color.componentToHex(g) + Color.componentToHex(b);
  454. }
  455. static hexToRgb(hex) {
  456. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  457. return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
  458. }
  459. }
  460. function colorString(str, colorOrRed, green, blue){
  461. var color = [0,0,0];
  462. if([colorOrRed, green, blue].every(v => typeof v == "number"))
  463. color = [colorOrRed, green, blue];
  464. else if(colorOrRed instanceof Array)
  465. color = colorOrRed;
  466. else{
  467. color = new BP_Color(colorOrRed, green, blue).toRGB();
  468. }
  469. return `\x1B[38;2;${color[0]};${color[1]};${color[2]}m${str}\x1B[0m`;
  470. }
  471. function convolve(func, ...partArrays){
  472. if(partArrays.length == 1 && !(partArrays[0] instanceof Array) && (typeof partArrays[0]) == "object")
  473. partArrays = Object.values(partArrays[0]);
  474. if(! partArrays?.length)
  475. return;
  476. var funcArgArrays = [];
  477. var firstParts = partArrays[0];
  478. var nextArgs = [];
  479. if(partArrays.length == 1){
  480. if(typeof func == "function"){
  481. for(let p of firstParts)
  482. func(p);
  483. }
  484. return firstParts;
  485. }
  486. var lastArray = false;
  487. if(partArrays.length > 2){
  488. nextArgs = convolve(false, ...partArrays.slice(1));
  489. }
  490. else{
  491. nextArgs = partArrays[1];
  492. lastArray = true;
  493. }
  494. for(let arg of firstParts){
  495. for(let otherArgs of nextArgs){
  496. if(lastArray)
  497. funcArgArrays.push([arg, otherArgs]);
  498. else
  499. funcArgArrays.push([arg, ...otherArgs]);
  500. if(typeof func == "function")
  501. func(arg, ...otherArgs);
  502. }
  503. }
  504. return funcArgArrays;
  505. }
  506. function copyToClipboard(text) {
  507. if (window.clipboardData && window.clipboardData.setData) {
  508. // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible.
  509. return window.clipboardData.setData("Text", text);
  510.  
  511. }
  512. else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
  513. var textarea = document.createElement("textarea");
  514. textarea.textContent = text;
  515. textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in Microsoft Edge.
  516. document.body.appendChild(textarea);
  517. textarea.select();
  518. try {
  519. document.execCommand("copy"); // Security exception may be thrown by some browsers.
  520. msg("Copied.", "success", "", 1000);
  521. return;
  522. }
  523. catch (ex) {
  524. console.warn("Copy to clipboard failed.", ex);
  525. return prompt("Copy to clipboard: Ctrl+C, Enter", text);
  526. }
  527. finally {
  528. document.body.removeChild(textarea);
  529. }
  530. }
  531. }
  532. if("undefined" == typeof bp_iObject){
  533. bp_iObject = {
  534. style: `<style id="bp_iO_style">
  535. div.bp_iObject{
  536. color: #fff;
  537. }
  538. div.bp_iObject div.bp_iObject{
  539. display: inline;
  540. vertical-align: text-top;
  541. }
  542. .bp_iObject div.bp_iO_inner{
  543. white-space: pre;
  544. border: 1px solid transparent;
  545. transition: all 0.5s;
  546. }
  547. .bp_iO_pseudoContent{
  548. margin: 0.4em;
  549. }
  550. .bp_iO_pseudoContent + .bp_iO_pseudoContent{
  551. margin-left: 0;
  552. }
  553. .bp_iO_pseudoContent:before{
  554. content: attr(data-value);
  555. }
  556. span.bp_iO_meta{
  557. opacity: 0.7;
  558. background: #333;
  559. border: 1px solid #888;
  560. border-radius: 0.3em;
  561. font-size: 66%;
  562. }
  563. span.bp_iO_indent:before{
  564. content: " ";
  565. white-space: pre;
  566. }
  567. span.bp_iO_comma{
  568. display: inline;
  569. vertical-align: bottom;
  570. }
  571. div.bp_iObject>.bp_iO_content{
  572. color: #bbb;
  573. white-space: pre;
  574. }
  575. div.bp_iObject[data-type="string"]>.bp_iO_content,
  576. div.bp_iObject span.quotes{
  577. color: #888;
  578. }
  579. span.bp_iO_key{
  580. font-style: italic;
  581. }
  582. .collapsible>span.bp_iO_key{
  583. cursor: pointer;
  584. }
  585. div.bp_iObject[data-type="string"]>.bp_iO_content>span.text{
  586. color: #fe8;
  587. }
  588. div.bp_iObject[data-type="number"]>.bp_iO_content{
  589. color: #5cf;
  590. }
  591. div.bp_iObject[data-type="RegExp"]>.bp_iO_content{
  592. color: #f48;
  593. }
  594. div.bp_iObject[data-type="boolean"]>.bp_iO_content{
  595. color: #08f;
  596. }
  597. div.bp_iObject.collapsed>.bp_iO_content{
  598. white-space: normal;
  599. }
  600. div.bp_iObject.collapsed .bp_iO_inner{
  601. display: inline-block;
  602. width: 1px;
  603. height: 1px;
  604. overflow: hidden;
  605. margin-left: -5px;
  606. opacity: 0.01;
  607. clip: rect(0 0 0 0);
  608. }
  609. div.bp_iObject.collapsible .bracket.closing{
  610. cursor: pointer;
  611. pointer-events: all;
  612. }
  613. div.bp_iObject.collapsed .bracket.closing:before{
  614. content: "... ";
  615. }
  616. .bp_iObject{
  617. position: relative;
  618. }
  619. .bp_iObject.collapsible>.bp_iO_key:before{
  620. content: "â–² ";
  621. position: absolute;
  622. display: inline-block;
  623. right: 100%;
  624. font-size: 66%;
  625. pointer-events: all;
  626. }
  627. .bp_iObject.collapsible.collapsed>.bp_iO_key:before{
  628. content: "â–¼ ";
  629. }
  630. .bp_iO_inner.editing{
  631. border: 1px inset #888;
  632. background: #000;
  633. color: #fff;
  634. padding: 0.5em;
  635. margin: 0.2em;
  636. }
  637. .bp_iO_copy{
  638. position: absolute;
  639. right: 0;
  640. top:0;
  641. opacity: 0;
  642. padding: 0.75em 1em;
  643. background: #666;
  644. color: #fff;
  645. border: 1px solid #fff;
  646. border-radius: 0.5em;
  647. cursor: pointer;
  648. transition: all 0.5s;
  649. }
  650. /* .bp_iO_copy::before{
  651. content: "\\002398 Copy";
  652. }
  653. .bp_iObject:hover>.bp_iO_copy{
  654. opacity: 0.3;
  655. }
  656. .bp_iObject:hover>.bp_iO_copy:hover{
  657. opacity: 1;
  658. background: #000;
  659. }*/ /* TODO: make palatable */
  660. .zeroHeight{
  661. line-height: 0;
  662. margin:0;
  663. padding:0;
  664. display: none;
  665. }
  666. .collapsed br.zeroHeight{
  667. display: inline;
  668. }
  669. .collapsedIndent{
  670. display: none;
  671. }
  672. .collapsed>.bp_iO_content>.bp_iO_inner>.collapsedIndent{
  673. display: inline;
  674. }
  675. </style>`,
  676. createInteractiveObject: function(obj, onlyOwnProperties=true, indentString="\t", objKey="", level=0, includeLabel = false){
  677. if("undefined" == typeof bp_iO)
  678. bp_iO = bp_iObject.setup();
  679. var indent = "";
  680. for(i=0; i<level; i++)
  681. indent += indentString;
  682. var q = "<span class='quotes'>&quot;</span>";
  683. var ell = "<span class='bp_iO_ellipsis'>&quot;</span>";
  684. var r = "";
  685. var t = typeof obj;
  686. var typeString = t.replace(/^\w/, (a)=> a.toUpperCase());
  687. var inner = "";
  688. var count=0;
  689. var isArray = obj instanceof Array;
  690. switch(typeof obj){
  691. case "object":
  692. var pt = ""; // + obj.toString(); // TODO: prototype class name?
  693. if(isArray)
  694. typeString = "Array";
  695. else if(obj instanceof RegExp){
  696. typeString = t = "RegExp";
  697. inner = obj.toString();
  698. break;
  699. }
  700. typeString += " " + pt;
  701. if(!obj || !obj.keys){
  702. inner = obj.toString();
  703. break;
  704. }
  705. var keys = Object.keys(obj);
  706. if(onlyOwnProperties){
  707. var okeys = keys;
  708. keys = [];
  709. for(let k of okeys){
  710. if(obj.hasOwnProperty(k))
  711. keys.push(k);
  712. }
  713. }
  714. count = keys.length;
  715. for(let i=0; i<keys.length; i++){
  716. let k = keys[i];
  717. 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 />"):"");
  718. }
  719. if(!isArray && count>0)
  720. inner = "<div class='bp_iO_inner'><br class='zeroHeight' />" + inner + "<br /><span class='collapsedIndent'>" + indent + "</span></div>";
  721. if(isArray)
  722. inner = "[" + inner + "]";
  723. else{
  724. inner = "{" + inner + (inner?indent:"") + "<span class='bracket closing'>}</span>";
  725. }
  726. break;
  727. case "string":
  728. inner += q + "<span class='text bp_iO_inner'>" + htmlEntities(obj) + "</span>" + q;
  729. break;
  730. default:
  731. inner += "<span class='bp_iO_inner'>" + htmlEntities(obj.toString()) + "</span>";
  732. }
  733. 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>`:"") + ":&nbsp;" :"") + "<span class='bp_iO_content'>" + inner + "</span>";
  734. 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>`;
  735. if(level <= 0){
  736. r = $(r)[0];
  737. r.bp_iO = {
  738. ref: obj,
  739. onlyOwnProperties,
  740. indentString,
  741. element: r,
  742. redraw: function(){
  743. var e = createInteractiveObject(this.ref, this.onlyOwnProperties, this.indentString);
  744. this.element.innerHTML = e.innerHTML;
  745. $(e).remove();
  746. // console.log(this.element);
  747. }
  748. };
  749. }
  750. return r;
  751. },
  752. setup: function(){
  753. if("undefined" == typeof bp_iO){
  754. bp_iO = this;
  755. $("head").append(this.style);
  756. $("body").on("click", ".bp_iO_copy", function(e){
  757. e.stopImmediatePropagation();
  758. // console.log(e, e.target);
  759. var r = $(e.target).parent().text();
  760. copyToClipboard(r);
  761. });
  762. $("body").on("click", ".collapsible>.bp_iO_key, .collapsible>.bp_iO_content>.bracket.closing", function(e){
  763. e.stopImmediatePropagation();
  764. // console.log(e, e.target);
  765. var p = $(e.target).parent();
  766. if(p.hasClass("bp_iO_content"))
  767. p = p.parent();
  768. p.toggleClass("collapsed");
  769. });
  770. $("body").on("dblclick", ".bp_iObject.root.editable .bp_iO_inner", function(e){
  771. // if(!$(e.target).is(e.currentTarget))
  772. // return;
  773. e.stopImmediatePropagation();
  774. // console.log(e, e.target);
  775. var o = $(e.currentTarget);
  776. o.addClass("editing");
  777. // console.log(e.target, o);
  778. o.attr("contenteditable", "true");
  779. o[0].beforeEdit = o.text().trim();
  780. o[0].focus();
  781. return false;
  782. });
  783. $(document).on("keydown", ".bp_iO_inner.editing", function(e){
  784. // console.log(e);
  785. var o = $(e.target);
  786. if(e.key=="Enter" && !e.shiftKey){
  787. // console.log("enter!");
  788. var val_inner = o.text().trim();
  789. var val_container = o.closest(".bp_iObject").first();
  790. var val_outer = o.parent().text().trim();
  791. // console.log("new value?", val_outer, val_inner);
  792. var path = [];
  793. var obj = o.parents(".bp_iObject.root");
  794. if(!obj || obj.length<=0 || !obj[0].bp_iO){
  795. console.error("Dang, no root found for", o);
  796. }
  797. else{
  798. try{
  799. obj = obj[0].bp_iO;
  800. var ref = obj.ref;
  801. var v = eval(`(function(){return ${val_outer};})();`);
  802. if(val_container.is(o.parents(".bp_iObject.root").first())){
  803. // console.log("yup, modifying root");
  804. for(let k in ref) // TODO: accommodate different types
  805. delete ref[k];
  806. for(let k in v)
  807. ref[k] = v[k];
  808. obj.redraw();
  809. }
  810. else{
  811. var ancestry = o.parentsUntil(".bp_iObject.root", ".bp_iObject").get();
  812. ancestry = ancestry.reverse();
  813. for(let i = 0; i<ancestry.length; i++){
  814. var p = ancestry[i];
  815. // var key = p.attr("data-key");
  816. var k = p.dataset.key;
  817. // console.log(k, p);
  818. if(i>=ancestry.length-1){
  819. // console.log(`(function(){return ${val_outer};})();`);
  820. ref[k] = v;
  821. var h = createInteractiveObject(ref[k], obj.onlyOwnProperties, obj.indentString, k, i+1, i+1>0);
  822. val_container.html(h);
  823. break;
  824. }
  825. ref = ref[k];
  826. path.push(k);
  827. }
  828. // console.log(obj, path)
  829. }
  830. }catch(e){
  831. console.error(e);
  832. }
  833. }
  834. }
  835. else if(e.key=="Escape"){
  836. // console.log("Esc!");
  837. o.text(o[0].beforeEdit);
  838. }
  839. else
  840. return;
  841. e.preventDefault();
  842. o.removeClass("editing");
  843. o.attr("contenteditable", "false");
  844. });
  845. createInteractiveObject = this.createInteractiveObject;
  846. return this;
  847. }
  848. }
  849. };
  850. }
  851. function dateString(date, format){
  852. if(date===undefined || date===null || date === "")
  853. date = new Date();
  854. if(format===undefined)
  855. format="YYYY-MM-DD HH:mm:SS";
  856. else if(format==="file")
  857. format="YYYY-MM-DD HH-mm-SS";
  858. date = new Date(date);
  859. var year = pad(date.getFullYear(), 4);
  860. var months = pad(date.getMonth() + 1);
  861. var days = pad(date.getDate());
  862. var hours = pad(date.getHours());
  863. var minutes = pad(date.getMinutes());
  864. var seconds = pad(date.getSeconds());
  865. return format.replace("YYYY", year)
  866. .replace("MM", months)
  867. .replace("DD", days)
  868. .replace("HH", hours)
  869. .replace("mm", minutes)
  870. .replace("SS", seconds);
  871. }
  872. function pad(num, digits=2){
  873. var r = String(num);
  874. if(r.padStart)
  875. return r.padStart(digits, "0");
  876. return ("0000000000" + r).slice(-digits);
  877. }
  878. function escapeHtml(str){
  879. return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  880. }
  881. String.prototype.escapeHtml = function(){
  882. return escapeHtml(this);
  883. };
  884. filterTables = {count: 0, hasInit: false};
  885. filterTables.init = function(filter="", override=false){
  886. if($("head #bp_filtertable").length<=0)
  887. $("head").append(`
  888. <style id="bp_filtertable">
  889. .filtertable.table,
  890. .filtertable .table {
  891. display: table;
  892. }
  893. .filtertable .tr {
  894. display: table-row;
  895. }
  896. .filtertable .td,
  897. .filtertable .th {
  898. display: table-cell;
  899. }
  900. .filtertable th,
  901. .filtertable .th {
  902. white-space: nowrap;
  903. word-break: keep-all;
  904. padding: 3px;
  905. text-align: left;
  906. font-weight: bold;
  907. }
  908. .filtertable .filterhide, .simplehide{
  909. display: none;
  910. }
  911. .filtertable th.filtering{
  912. background-color: rgba(200,20,0,0.3);
  913. }
  914. .filtertable .columnFilter{
  915. display: inline-block;
  916. vertical-align: middle;
  917. width: calc(100% - 20px);
  918. }
  919. .filtertable .columnFilter::placeholder{
  920. font-weight: 300;
  921. }
  922. .filtertable .sorter{
  923. display: inline-block;
  924. vertical-align: middle;
  925. margin-left: 3px
  926. }
  927. .filtertable .sort{
  928. padding: 1px 4px;
  929. line-height: 9px;
  930. font-size: 10px;
  931. background-color: rgba(140,140,140,0.5);
  932. color: #ccc;
  933. cursor: pointer;
  934. opacity: 0.5;
  935. }
  936. .filtertable .sort+.sort{
  937. margin-top:2px;
  938. }
  939. .filtertable .sort.desc:after{
  940. content: "â–¼";
  941. }
  942. .filtertable .sort.asc:after{
  943. content: "â–²";
  944. }
  945. .filtertable .sort:hover{
  946. opacity: 1;
  947. }
  948. .filtertable .sort.active{
  949. background-color: #160;
  950. color: #fff;
  951. }
  952. .filtertable .clearfilter{
  953. display: inline-block;
  954. vertical-align: middle;
  955. margin-left: -15px;
  956. padding: 0 3px 4px;
  957. background: rgba(120,10,0,1);
  958. color: #fff;
  959. opacity: 0.5;
  960. cursor: pointer;
  961. }
  962. .filtertable .clearfilter:hover{
  963. opacity: 1;
  964. }
  965. </style>
  966. `);
  967. $(".filtertable" + filter + ":not(.hasSetup)").each(function(){
  968. makeTableSortable(this, true);
  969. });
  970. if(filterTables.hasInit && !override)
  971. return;
  972. $("body").on("keyup", ".filtertable .columnFilter", function(e){
  973. // console.log(this, e);
  974. var t = $(this).closest("table, .table");
  975. if(!t || t.length<1)
  976. return console.error("Table not found filtering");
  977. var headers = t[0].filterHeaders;
  978. if(!headers)
  979. headers = t;
  980. var cont = t.children("tbody, .tbody");
  981. if(cont && cont.length>0)
  982. t = cont.first();
  983. var n = $(this).attr("data-colnum");
  984. var o = this;
  985. clearTimeout(filterTables.tuFilter);
  986. filterTables.tuFilter = setTimeout(function(){
  987. // console.log("filtering", n, t);
  988. var f = o.value;
  989. var fheader = $(headers).find("th:nth-child(" + n + "), .th:nth-child(" + n + ")");
  990. if(f == ""){
  991. $(t).find("tr, .tr").each(function(){
  992. if(filterTables.setColumnFilter(this, n, false))
  993. filterTables.showhideFilter(this);
  994. });
  995. $(fheader).removeClass("filtering");
  996. return;
  997. }
  998. $(fheader).addClass("filtering");
  999. $(t).find("tr, .tr").each(function(){
  1000. if(this == fheader || $(this).hasClass("sortHeaderRow"))
  1001. return;
  1002. var c = $(this).find("td:nth-child(" + n + "), .td:nth-child(" + n + ")");
  1003. // console.log("got cells?", c);
  1004. if(c.length > 0){
  1005. var s = c.text();
  1006. // console.log("compare", f, "against", s, "in", n+" -> nth child");
  1007. var fs = f.substr(0, 1);
  1008. var fc = true;
  1009. if(fs == "<" && f.length > 1){
  1010. // console.log("is <");
  1011. fc = s * 1 >= f.substr(1) * 1;
  1012. } else if(fs == ">" && f.length > 1){
  1013. // console.log("is >");
  1014. fc = s * 1 <= f.substr(1) * 1;
  1015. } else {
  1016. fc = !s.toLowerCase().includes(f.toLowerCase());
  1017. }
  1018. if(filterTables.setColumnFilter(this, n, fc)){
  1019. filterTables.showhideFilter(this);
  1020. }
  1021. }
  1022. });
  1023. }, 100);
  1024. });
  1025.  
  1026. $("body").on("click", ".filtertable .sort", function(){
  1027. var isactive = !$(this).hasClass("active");
  1028. filterTables.intable(this, ".sort").removeClass("active");
  1029. $(this).toggleClass("active", isactive);
  1030. var sortcol = 0;
  1031. var asc = true;
  1032. if(isactive){
  1033. sortcol = $(this).parent().attr("data-colnum");
  1034. asc = $(this).hasClass("asc");
  1035. }
  1036. filterTables.sortTable($(this).closest("table, .table").first(), sortcol, asc);
  1037. });
  1038. $("body").on("click", ".clearfilter", function(){
  1039. var fi = $(this).parent().find(".columnFilter");
  1040. // console.log("clear", fi);
  1041. // if(fi.length>0){
  1042. $(fi).val("");
  1043. $(fi).keyup();
  1044. // }
  1045. });
  1046.  
  1047. $("body").on("click", "table.minimized tr, .table.minimized .tr", function(){
  1048. $(this).parent().find("tr.active, .tr.active").removeClass("active");
  1049. $(this).addClass("active");
  1050. });
  1051. setTimeout(filterTables.resizeFilters, 1000);
  1052. setInterval(filterTables.resizeFilters, 5000);
  1053. filterTables.hasInit = true;
  1054. };
  1055.  
  1056. filterTables.tuFilter = null;
  1057.  
  1058. filterTables.setColumnFilter = function(e, c, onoff){
  1059. var cols = [];
  1060. var ec = $(e).attr("filteredCols");
  1061. if(undefined !== ec && ec != "")
  1062. cols = ec.split(",");
  1063. var i = cols.indexOf(c);
  1064. // console.log("Set filter for column " + c + " to " + onoff.toString() + " on: ", e);
  1065. if(i < 0 == onoff){
  1066. if(onoff){
  1067. cols.push(c);
  1068. // console.log("column filter added");
  1069. } else {
  1070. // console.log("remove column filter:");
  1071. var a = cols.splice(i, 1);
  1072. // console.log("a:", a);
  1073. // console.log("cols:", cols);
  1074. }
  1075. $(e).attr("filteredCols", cols.join(","));
  1076. // console.log("Needed change, is now: " + $(e).attr("filteredCols"));
  1077. return true;
  1078. }
  1079. // console.log("no change");
  1080. return false;
  1081. };
  1082.  
  1083. filterTables.showhideFilter = function(e){
  1084. var ec = $(e).attr("filteredCols");
  1085. if(undefined !== ec && ec != "")
  1086. $(e).addClass("filterhide");
  1087. else
  1088. $(e).removeClass("filterhide");
  1089. };
  1090.  
  1091. filterTables.sortTable = function(table, col, asc){
  1092. var tbody = $(table).children("tbody, .tbody");
  1093. if(tbody.length>0)
  1094. table = tbody;
  1095. var tosort = $(table).children("tr.sortable, .tr.sortable");
  1096. var ifasc = asc ? 1 : -1;
  1097. // console.log("sorting:", table, tosort);
  1098. tosort.sort(function(a, b){
  1099. var ca, cb;
  1100. if(col === 0){
  1101. ca = $(a).attr("nosort");
  1102. cb = $(b).attr("nosort");
  1103. } else {
  1104. ca = $(a).find("td:nth-child(" + col + "), .td:nth-child(" + col + ")").text();
  1105. cb = $(b).find("td:nth-child(" + col + "), .td:nth-child(" + col + ")").text();
  1106. }
  1107. ca = ca.trim().toLowerCase();
  1108. cb = cb.trim().toLowerCase();
  1109. if((ca*1).toString() == ca.toString() && (cb*1).toString() == cb.toString()){
  1110. // console.log("is numeric");
  1111. return (ca*1 - cb*1) * ifasc;
  1112. }
  1113. else{
  1114. var rex = /\s*(\d+)(.*)/;
  1115. var ma = ca.match(rex);
  1116. var mb = cb.match(rex);
  1117. if(ma && mb){
  1118. // console.log("oh, numbers!", ma, mb);
  1119. var diff = ma[1] - mb[1];
  1120. if(diff!=0)
  1121. return diff * ifasc;
  1122. }
  1123. }
  1124. if(ca > cb)
  1125. return 1 * ifasc;
  1126. if(ca < cb)
  1127. return -1 * ifasc;
  1128. return 0;
  1129. });
  1130. // $(table).children("tr.sortable, .tr.sortable").remove();
  1131. $(table).append(tosort);
  1132. };
  1133.  
  1134. filterTables.intable = function(el, s){
  1135. return $(el).closest("table, .table").first().find(s);
  1136. };
  1137.  
  1138. filterTables.resizeFilters = function(){
  1139. $(".shrink .columnFilter").each(function(){
  1140. var o = this;
  1141. filterTables.shrinkifbigger(o);
  1142. });
  1143. };
  1144.  
  1145. filterTables.shrinkifbigger = function(o){
  1146. var p = $(o).parent().parent();
  1147. var w = p.width() - 22;
  1148. if(w<40)
  1149. w = 40;
  1150. if(w > $(p).width()-20)
  1151. return;
  1152. $(o).css("width", w+"px");
  1153. // setTimeout(function(){
  1154. if(w > $(p).width()-20){
  1155. filterTables.shrinkifbigger(o);
  1156. // console.log("shrink: " + p.text());
  1157. }
  1158. else{
  1159. // console.log("is max: " + p.text);
  1160. }
  1161. // }, 1);
  1162. };
  1163.  
  1164. function makeTableSortable(element, hasInit=false){
  1165. var table = $(element);
  1166. var headers;
  1167. if(table.is("tr, .tr")){
  1168. headers = table;
  1169. table = headers.closest("table, .table");
  1170. }
  1171. else{
  1172. let cont = table.children("thead, .thead, tbody, .tbody").first();
  1173. if(!cont || cont.length<=0)
  1174. cont = table;
  1175. headers = cont.find(">tr>th, >.tr>th, >tr>.th, >.tr>.th");
  1176. if(headers && headers.length>0)
  1177. headers = headers.parent();
  1178. else
  1179. headers = cont.find(">tr, >.tr").first();
  1180. }
  1181. headers.addClass("sortHeaderRow").children("td, .td").addClass("th"); // in case they're just td, .td
  1182. table.addClass("filtertable sortable");
  1183. table[0].filterHeaders = headers;
  1184. filterTables.count++;
  1185. var c = 0;
  1186. $(headers).children("th, .th").each(function(){
  1187. var h = $(this).html();
  1188. c++;
  1189. var w = $(this).width() - 16;
  1190. if(w<40)
  1191. w = 40;
  1192. $(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");
  1193. });
  1194. c = 0;
  1195. var cont = table.children("tbody, .tbody");
  1196. if(!cont || cont.length<1)
  1197. cont = table;
  1198. $(cont).children("tr:not(.sortHeaderRow), .tr:not(.sortHeaderRow)").each(function(){
  1199. // if(c > 0){
  1200. $(this).addClass("sortable");
  1201. $(this).attr("nosort", c);
  1202. // }
  1203. c++;
  1204. });
  1205. table.addClass("hasSetup");
  1206. if(!filterTables.hasInit && !hasInit)
  1207. filterTables.init();
  1208. }
  1209. function filteredDeepCopy(obj, func, maxDepth=10, circ=[]){
  1210. var out = {};
  1211. if(obj instanceof Array)
  1212. out = [];
  1213. for(let k in obj){
  1214. if(!Object.hasOwn(obj, k))
  1215. continue;
  1216. let v = obj[k];
  1217. if(typeof func == "function"){
  1218. let o = func(k, v);
  1219. if(!o)
  1220. continue;
  1221. if(o instanceof Array){
  1222. k = o[0];
  1223. v = o[1];
  1224. }
  1225. }
  1226. if(typeof v == "object"){
  1227. if(maxDepth<=0 || circ.includes(v))
  1228. continue;
  1229. maxDepth--;
  1230. out[k] = filteredDeepCopy(v, func, maxDepth, [...circ, v]);
  1231. continue;
  1232. }
  1233. out[k] = v;
  1234. }
  1235. return out;
  1236. }
  1237. var bpTitleFormats = {
  1238. movies: [
  1239. "[title] [[year]]",
  1240. "[title] ([year])",
  1241. "[title] - [year]"
  1242. ],
  1243. series: [
  1244. "[name] - Season [season] Episode [episode] - [title]",
  1245. "[name] - Season [season] Episode [episode]",
  1246. "[name] - S[lzseason]E[lzepisode] - [title]",
  1247. "[name] - S[lzseason]E[lz3episode] - [title]"
  1248. ]
  1249. };
  1250.  
  1251. var bpMediaTitleRegex = {
  1252. movies: [
  1253. /(.+) \[(\d{4})\]$/mi,
  1254. /(.+) \((\d{4})\)$/mi,
  1255. /(.+) - (\d{4})$/mi,
  1256. ],
  1257. series: [
  1258. // /(.+?) ?-? (?:(?:season |S)?0?0?(\d+))? ?(?:episode |E|x)0*(\d+(?:-\d+)?)(?: ?[:-]? (.+))?/mi
  1259. /(.+?) ?[:,-]? (?:(?:season |S)?0*(\d+))?\s*[,-]?\s*(?:episode |E|x)0*(\d+(?:-\d+)?)(?: ?[:, -]* (.+))?/mi
  1260. ]
  1261. };
  1262. function testEpisodeTitleRex(rex){
  1263. var values = {
  1264. name: ["Some series name", "Test? <^> \"Toast\""],
  1265. season: ["0", "03", "102"],
  1266. episode: ["0", "03", "102", "13-14"],
  1267. title: ["and a title", "\"and a title\"", " - and a title"],
  1268. joiners: [",", ":", " :", "-", " -", ""],
  1269. altjoiners: [",", "-", " -"],
  1270. sseason: ["season ", "s"],
  1271. sepisode: ["episode ", "e"]
  1272. };
  1273. if(!rex)
  1274. rex = bpMediaTitleRegex.series[0];
  1275. var testStrings = [];
  1276. convolve((name, season, episode, title, joiner, altjoiner, sseason, sepisode)=>{
  1277. testStrings.push(`${name}${joiner} ${sseason}${season}${altjoiner} ${sepisode}${episode}${joiner} ${title}`);
  1278. testStrings.push(`${name}${joiner} ${sseason}${season}${altjoiner}${sepisode}${episode}${joiner} ${title}`);
  1279. testStrings.push(`NoTitle${name}${joiner} ${sseason}${season}${altjoiner} ${sepisode}${episode}`);
  1280. testStrings.push(`NoTitle${name}${joiner} ${sseason}${season}${altjoiner}${sepisode}${episode}`);
  1281. testStrings.push(`${name} ${season}x${episode}${joiner} ${title}`);
  1282. testStrings.push(`NoTitle${name} ${season}x${episode}`);
  1283. },
  1284. values
  1285. );
  1286. // log(testStrings);
  1287. var colorOk = "#66ff55";
  1288. var colorWarn = "#cccc55";
  1289. var colorBad = "#ff6655";
  1290. var testOk = [];
  1291. var testFail = [];
  1292. for(let s of testStrings){
  1293. s = s.replace(/\s+/g, " ");
  1294. var m = s.match(rex);
  1295. // log(r);
  1296. if(!m || !m.length){
  1297. log(colorString(s, colorWarn), colorString("Failed RegEx test", colorBad));
  1298. testFail.push([s, "Failed to match RegEx"]);
  1299. }
  1300. else{
  1301. // log(colorString(s, colorOk));
  1302. var name = m[1];
  1303. var season = m[2];
  1304. var episode = m[3];
  1305. var title = m[4];
  1306. if(title){
  1307. title = title.trim().replace(/^[,: -]+/g, "");
  1308. title = title.trim().replace(/^"(.+)"$/, "$1");
  1309. }
  1310. var isNoTitle = false;
  1311. if(name.startsWith("NoTitle")){
  1312. name = name.replace(/^NoTitle/, "");
  1313. isNoTitle = true;
  1314. }
  1315. if(!values.name.includes(name)){
  1316. testFail.push([s, "Failed NAME"]);
  1317. continue;
  1318. }
  1319. if(!["0", "3", "102"].includes(season)){
  1320. testFail.push([s, "Failed SEASON"]);
  1321. continue;
  1322. }
  1323. if(!["0", "3", "102", "13-14"].includes(episode)){
  1324. testFail.push([s, "Failed EPISODE"]);
  1325. continue;
  1326. }
  1327. if(!["and a title"].includes(title) && !isNoTitle){
  1328. testFail.push([s, "Failed TITLE"]);
  1329. continue;
  1330. }
  1331. testOk.push(s);
  1332. }
  1333. }
  1334. for(let f of testFail)
  1335. log(f[0], colorString(f[1], colorBad));
  1336. log(colorString("Test OK: " + testOk.length, colorOk), colorString("Test Failed: " + testFail.length, testFail.length?colorBad:colorOk));
  1337. }
  1338.  
  1339. function guessMovieOrTV(title){
  1340. var tit = title.replace(/[—–]/g, "-"); // em-dash, en-dash
  1341. for(let rex of bpMediaTitleRegex.series){
  1342. if(rex.test(tit))
  1343. return "TV";
  1344. }
  1345. for(let rex of bpMediaTitleRegex.movies){
  1346. if(rex.test(tit))
  1347. return "Movie";
  1348. }
  1349. return false;
  1350. }
  1351.  
  1352. function formatMovieTV(tit, templateSeries, templateMovie){
  1353. switch(guessMovieOrTV(tit)){
  1354. case "TV":
  1355. return formatEpisodeTitle(tit, templateSeries);
  1356. case "Movie":
  1357. return formatMovieTitle(tit, templateMovie);
  1358. default:
  1359. console.log("Could not identify TV or Movie title");
  1360. return tit;
  1361. }
  1362. }
  1363.  
  1364. function formatMovieTitle(tit, template){
  1365. if(!template)
  1366. template = bpTitleFormats.movies[0];
  1367. // console.log("preferred format: " + template);
  1368. var match = false;
  1369. for(let rex of bpMediaTitleRegex.movies){
  1370. match = tit.match(rex);
  1371. if(match){
  1372. // match = rex.exec(tit);
  1373. console.log("title matches format " + rex.toString(), match);
  1374. break;
  1375. }
  1376. }
  1377. if(!match){
  1378. console.log("Title format not recognized", tit);
  1379. return tit;
  1380. }
  1381. var name = match[1];
  1382. var year = match[2];
  1383. tit = template.replace("[title]", name)
  1384. .replace("[year]", year);
  1385. console.log("formatted title:", tit);
  1386. return tit;
  1387. }
  1388.  
  1389. function formatEpisodeTitle(tit, template){
  1390. if(!template)
  1391. template = bpTitleFormats.series[0];
  1392. // console.log("preferred format: " + template);
  1393. tit = tit.replace(/[—–]/g, "-"); // em-dash, en-dash
  1394. var match = false;
  1395. for(let rex of bpMediaTitleRegex.series){
  1396. match = tit.match(rex);
  1397. if(match){
  1398. // match = rex.exec(tit);
  1399. console.log("title matches format " + rex.toString(), match);
  1400. break;
  1401. }
  1402. }
  1403. if(!match){
  1404. console.log("Title format not recognized", tit);
  1405. return tit;
  1406. }
  1407. var name = match[1];
  1408. var season = match[2];
  1409. if(!season)
  1410. season = 1;
  1411. var episode = match[3];
  1412. var title = (match.length>=5 && match[4] !== undefined)? match[4] : "";
  1413. if((/(Episode #? ?\d+|S\d+ ?E\d+)/i).test(title))
  1414. title = "";
  1415. // console.log({"name" : name, "season" : season, "episode" : episode, "title" : title});
  1416. if(title===""){
  1417. template = template.replace(/ ?-? \[title\]/, "");
  1418. console.log("no title:", template);
  1419. }
  1420. tit = template.replace("[name]", name)
  1421. .replace("[season]", season)
  1422. .replace("[episode]", episode)
  1423. .replace(/\[lz(\d*)season]/i, function(_,p){
  1424. if(p==="")
  1425. p = 2;
  1426. return lz(season, p);
  1427. })
  1428. .replace(/\[lz(\d*)episode]/i, function(_,p){
  1429. if(p==="")
  1430. p = 2;
  1431. return lz(episode, p);
  1432. })
  1433. .replace("[title]", title);
  1434. console.log("formatted title:", tit);
  1435. return tit;
  1436. }
  1437.  
  1438. function sanitize(str){
  1439. str = str.replace(/[\\]/g, "-")
  1440. .replace(/["]/g, "'")
  1441. .replace(/\?\?/g, "⁇")
  1442. .replace(/\?/g, "︖")
  1443. .replace(/\s*[/:]\s*/g, " - ")
  1444. .replace(/\s+-\s*(?:-+\s+)+/g, " - ")
  1445. .replace(/\s+/g, " ");
  1446. return str.trim();
  1447. }
  1448.  
  1449. function lz(num, places = 2){
  1450. return ("0000000000" + num).slice(-places);
  1451. }
  1452.  
  1453. function varToPretty(str, casing="title", isSecond=false){
  1454. str = str
  1455. .replace(/(?:([^A-Z])([A-Z]))|(?:([a-zA-Z])([^a-zA-Z]))/g, "$1$3 $2$4")
  1456. .replace(/_/g, " ")
  1457. .replace(/\s\s+/g, " ")
  1458. .replace(/(max|min)/gi, "$1imum");
  1459. if(casing == "title")
  1460. str = str.replace(/(^|\s+)([a-z])/g, (_, a, b) => a + b.toUpperCase());
  1461. if(isSecond)
  1462. return str;
  1463. return varToPretty(str, casing, true);
  1464. }
  1465.  
  1466. function toTitleCase(str, preserveCaps=false, preserveAllCaps=false){
  1467. return str.replace(/\w[^\s_:-]*/g, function(txt){
  1468. var rest = txt.substr(1);
  1469. if(!preserveCaps){
  1470. if(preserveAllCaps){
  1471. if(txt.charAt(0) != txt.charAt(0).toUpperCase()|| rest != rest.toUpperCase())
  1472. rest = rest.toLowerCase();
  1473. }
  1474. else
  1475. rest = rest.toLowerCase();
  1476. }
  1477. return txt.charAt(0).toUpperCase() + rest;
  1478. });
  1479. }
  1480. const mimeTypes = {
  1481. ".aac": "audio/aac",
  1482. ".abw": "application/x-abiword",
  1483. ".arc": "application/x-freearc",
  1484. ".avif": "image/avif",
  1485. ".avi": "video/x-msvideo",
  1486. ".azw": "application/vnd.amazon.ebook",
  1487. ".bin": "application/octet-stream",
  1488. ".bmp": "image/bmp",
  1489. ".bz": "application/x-bzip",
  1490. ".bz2": "application/x-bzip2",
  1491. ".cda": "application/x-cdf",
  1492. ".csh": "application/x-csh",
  1493. ".css": "text/css",
  1494. ".csv": "text/csv",
  1495. ".doc": "application/msword",
  1496. ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  1497. ".eot": "application/vnd.ms-fontobject",
  1498. ".epub": "application/epub+zip",
  1499. ".gz": "application/gzip",
  1500. ".gif": "image/gif",
  1501. ".htm": "text/html",
  1502. ".html": "text/html",
  1503. ".ico": "image/vnd.microsoft.icon",
  1504. ".ics": "text/calendar",
  1505. ".jar": "application/java-archive",
  1506. ".jpeg.jpg": "image/jpeg",
  1507. ".js": "text/javascript",
  1508. ".json": "application/json",
  1509. ".jsonld": "application/ld+json",
  1510. ".mid.midi": "audio/midi",
  1511. ".mjs": "text/javascript",
  1512. ".mp3": "audio/mpeg",
  1513. ".mp4": "video/mp4",
  1514. ".mpeg": "video/mpeg",
  1515. ".mpkg": "application/vnd.apple.installer+xml",
  1516. ".odp": "application/vnd.oasis.opendocument.presentation",
  1517. ".ods": "application/vnd.oasis.opendocument.spreadsheet",
  1518. ".odt": "application/vnd.oasis.opendocument.text",
  1519. ".oga": "audio/ogg",
  1520. ".ogv": "video/ogg",
  1521. ".ogx": "application/ogg",
  1522. ".opus": "audio/opus",
  1523. ".otf": "font/otf",
  1524. ".png": "image/png",
  1525. ".pdf": "application/pdf",
  1526. ".php": "application/x-httpd-php",
  1527. ".ppt": "application/vnd.ms-powerpoint",
  1528. ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  1529. ".rar": "application/vnd.rar",
  1530. ".rtf": "application/rtf",
  1531. ".sh": "application/x-sh",
  1532. ".svg": "image/svg+xml",
  1533. ".swf": "application/x-shockwave-flash",
  1534. ".tar": "application/x-tar",
  1535. ".tif": "image/tiff",
  1536. ".tiff": "image/tiff",
  1537. ".ts": "video/mp2t",
  1538. ".ttf": "font/ttf",
  1539. ".txt": "text/plain",
  1540. ".vsd": "application/vnd.visio",
  1541. ".wav": "audio/wav",
  1542. ".weba": "audio/webm",
  1543. ".webm": "video/webm",
  1544. ".webp": "image/webp",
  1545. ".woff": "font/woff",
  1546. ".woff2": "font/woff2",
  1547. ".xhtml": "application/xhtml+xml",
  1548. ".xls": "application/vnd.ms-excel",
  1549. ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  1550. ".xml": "application/xml",
  1551. ".xul": "application/vnd.mozilla.xul+xml",
  1552. ".zip": "application/zip",
  1553. ".3gp": "video/3gpp",
  1554. ".3g2": "video/3gpp2",
  1555. ".7z": "application/x-7z-compressed"
  1556. };
  1557.  
  1558. function getContentTypeByExtension(ext){
  1559. var defaultType = "application/octet-stream";
  1560. if(typeof ext != "string")
  1561. return console.warn('[getContentTypeByExtension] Invalid type supplied, please supply string', ext) || defaultType;
  1562. if(ext.indexOf(".")<0)
  1563. ext = "." + ext;
  1564. else
  1565. ext = ext.replace(/^.*(?=[.]\w+$)/, '');
  1566. var mime = mimeTypes[ext.toLowerCase()];
  1567. if(!mime)
  1568. return console.warn('[getContentTypeByExtension] Failed to resolve for content name: %s', ext) || defaultType;
  1569. return mime;
  1570. }
  1571. function getParam(s){
  1572. return getParamFromString(location.href, s);
  1573. }
  1574.  
  1575. function getParamFromString(u, s){
  1576. var url = new URL(u);
  1577. return url.searchParams.get(s);
  1578. }
  1579. function getProperties(obj, filter=false, skipNative = true, _stringify=false){
  1580. var r = {};
  1581. var i = 0;
  1582. for(let k in obj){
  1583. let v = obj[k];
  1584. let include = true;
  1585. switch(typeof filter){
  1586. case "function":
  1587. include = filter(v);
  1588. break;
  1589. case "object":
  1590. if(filter instanceof RegExp)
  1591. include = filter.test(k);
  1592. break;
  1593. case "string":
  1594. let types = filter.split(/[, ]/);
  1595. include = false;
  1596. let inverted = false;
  1597. for(let t of types){
  1598. t = t.trim();
  1599. if(t == "")
  1600. continue;
  1601. if(t.substr(0,1)=="!"){
  1602. inverted = true;
  1603. t = t.substr(1);
  1604. }
  1605. if((t == "function" && v && v.call)){
  1606. if(!inverted){
  1607. v = v.toString();
  1608. include = !skipNative || v.indexOf("[native code]")<0;
  1609. }
  1610. else
  1611. include = false;
  1612. break;
  1613. }
  1614. else if((t == typeof v) ^ inverted){
  1615. include = true;
  1616. break;
  1617. }
  1618. }
  1619. break;
  1620. }
  1621. if(include){
  1622. if(_stringify && (typeof v != "string")){
  1623. let maybeRemoveQuotes = false;
  1624. try{
  1625. v = JSON.stringify(v, function(k, v){
  1626. if(typeof v == "function" || (v && v.call)){
  1627. v = v.toString();
  1628. maybeRemoveQuotes = true;
  1629. return v;
  1630. }
  1631. return v;
  1632. }, "\t");
  1633. }catch(e){
  1634. maybeRemoveQuotes = false;
  1635. v = v.toString();
  1636. }
  1637. if(maybeRemoveQuotes && typeof v == "string" && v.startsWith("\"") && v.endsWith("\""))
  1638. v = v.substring(1, v.length-1);
  1639. }
  1640. r[k] = v;
  1641. }
  1642. }
  1643. return r;
  1644. }
  1645. function getSelectedElements(){
  1646. var allSelected = [];
  1647. try{
  1648. var selection = window.getSelection();
  1649. var range = selection.getRangeAt(0);
  1650. if(range.startOffset == range.endOffset)
  1651. return allSelected;
  1652. var cont = range.commonAncestorContainer;
  1653. if(!cont){
  1654. // console.log("no parent container?");
  1655. return range.startContainer;
  1656. }
  1657. if(!cont.nodeName || cont.nodeName == "#text" || !cont.getElementsByTagName){
  1658. var p = cont.parentElement;
  1659. // console.log("weird container or text node; return parent", cont, p);
  1660. if(!p){
  1661. // console.log("actually, never mind; has no parent. Return element instead");
  1662. return [cont];
  1663. }
  1664. return [p];
  1665. }
  1666. var allWithinRangeParent = cont.getElementsByTagName("*");
  1667.  
  1668. for (var i=0, el; el = allWithinRangeParent[i]; i++){ // jshint ignore:line
  1669. // The second parameter says to include the element
  1670. // even if it's not fully selected
  1671. if (selection.containsNode(el, true))
  1672. allSelected.push(el);
  1673. }
  1674. }catch(e){
  1675. console.log(e);
  1676. }
  1677. return allSelected;
  1678. }
  1679. function htmlEntities(str, nl2br=false){
  1680. str = str.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
  1681. return '&#' + i.charCodeAt(0) + ';';
  1682. });
  1683. if(nl2br)
  1684. str = str.replace(/\r?\n/g, "<br />");
  1685. return str;
  1686. }
  1687. function isNativeFunction(value) {
  1688. // Used to resolve the internal `[[Class]]` of values
  1689. var toString = Object.prototype.toString;
  1690.  
  1691. // Used to resolve the decompiled source of functions
  1692. var fnToString = Function.prototype.toString;
  1693.  
  1694. // Used to detect host constructors (Safari > 4; really typed array specific)
  1695. var reHostCtor = /^\[object .+?Constructor\]$/;
  1696.  
  1697. // Compile a regexp using a common native method as a template.
  1698. // We chose `Object#toString` because there's a good chance it is not being mucked with.
  1699. var reNative = RegExp('^' +
  1700. // Coerce `Object#toString` to a string
  1701. String(toString)
  1702. // Escape any special regexp characters
  1703. .replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&')
  1704. // Replace mentions of `toString` with `.*?` to keep the template generic.
  1705. // Replace thing like `for ...` to support environments like Rhino which add extra info
  1706. // such as method arity.
  1707. .replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
  1708. );
  1709. var type = typeof value;
  1710. return type == 'function'
  1711. // Use `Function#toString` to bypass the value's own `toString` method
  1712. // and avoid being faked out.
  1713. ? // jshint ignore:line
  1714. reNative.test(fnToString.call(value))
  1715. // Fallback to a host object check because some environments will represent
  1716. // things like typed arrays as DOM methods which may not conform to the
  1717. // normal native pattern.
  1718. :
  1719. (value && type == 'object' && reHostCtor.test(toString.call(value))) || false;
  1720. }
  1721. function isPrimitive(obj){
  1722. if(typeof obj == "undefined" || obj === null)
  1723. return true;
  1724. if(typeof obj == "object" || typeof obj == "function")
  1725. return false;
  1726. return true;
  1727. }
  1728. function hasJQuery(){
  1729. if(typeof $ == "function" && typeof $.prototype == "object" && typeof $.fn != "undefined")
  1730. return $;
  1731. if(typeof jQuery == "function" && typeof jQuery.prototype == "object" && typeof jQuery.fn != "undefined")
  1732. return jQuery;
  1733. return false;
  1734. }
  1735.  
  1736. function isJQuery(obj){
  1737. if(!obj || typeof obj != "object")
  1738. return false;
  1739. var jq = hasJQuery();
  1740. if(!jq)
  1741. return false;
  1742. return obj instanceof jq;
  1743. }
  1744.  
  1745. function jqExtend(jQ=false){
  1746. if(!jQ)
  1747. jQ = hasJQuery();
  1748. if(jQ){
  1749. var fn = jQ.fn;
  1750. fn.selectText = function(){
  1751. var doc = document;
  1752. for(var i = 0; i<this.length; i++){
  1753. var element = this[i];
  1754. var range;
  1755. if (doc.body.createTextRange){
  1756. range = document.body.createTextRange();
  1757. range.moveToElementText(element);
  1758. range.select();
  1759. } else if (window.getSelection){
  1760. var selection = window.getSelection();
  1761. range = document.createRange();
  1762. range.selectNodeContents(element);
  1763. selection.removeAllRanges();
  1764. selection.addRange(range);
  1765. }
  1766. }
  1767. };
  1768.  
  1769. fn.fare = function(){
  1770. $(this).fadeOut(function(){
  1771. $(this).remove();
  1772. });
  1773. };
  1774.  
  1775. fn.textOnly = function(trim=true){
  1776. var c = this.clone();
  1777. c.children().remove();
  1778. if(trim)
  1779. return c.text().trim();
  1780. return c.text();
  1781. };
  1782. }
  1783. // else
  1784. // console.log("no jQuery, no extensions :(");
  1785. }
  1786. jqExtend();
  1787. class BPLogger {
  1788. constructor(name = false, prefix = false, debugging = false, level = 1){
  1789. if(!name && typeof GM_info !== "undefined")
  1790. name = GM_info.script.name;
  1791. if(prefix === false && name)
  1792. prefix = name;
  1793. if(!name)
  1794. name = "Logger";
  1795. this.name = name;
  1796. this.prefix = prefix;
  1797. this.debugging = debugging;
  1798. this.level = level;
  1799.  
  1800. this.colors = {
  1801. default: [180, 100],
  1802. warn: [60, 100],
  1803. error: [0, 100],
  1804. success: [150, 100]
  1805. };
  1806. this.history = [];
  1807. this.keepHistory = false;
  1808.  
  1809. if (typeof name == "object"){
  1810. Object.assign(this, name);
  1811. }
  1812. return this;
  1813. }
  1814.  
  1815. writeLog(args, type = "default", level = 1) {
  1816. if (this.keepHistory)
  1817. this.history.push([Date.now(), type, level, args]);
  1818. if (level > this.level)
  1819. return;
  1820. if(this.prefix)
  1821. 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];
  1822. if (this.debugging)
  1823. args = [...args, new Error().stack.replace(/^\s*(Error|Stack trace):?\n/gi, "").replace(/^([^\n]*\n)/, "\n")];
  1824. if(["warn", "error"].includes(type))
  1825. console[type](...args);
  1826. else
  1827. console.log(...args);
  1828. }
  1829. log(...args){
  1830. this.writeLog(args);
  1831. }
  1832. warn(...args){
  1833. this.writeLog(args, "warn");
  1834. }
  1835. error(...args){
  1836. this.writeLog(args, "error");
  1837. }
  1838. success(...args){
  1839. this.writeLog(args, "success");
  1840. }
  1841. }
  1842.  
  1843. function BPLogger_default(...args){
  1844. if(args.length<=0)
  1845. args = "";
  1846. var logger = new BPLogger(args);
  1847. log = function(...args){
  1848. logger.log(...args);
  1849. };
  1850. warn = function(...args){
  1851. logger.warn(...args);
  1852. };
  1853. error = function(...args){
  1854. logger.error(...args);
  1855. };
  1856. success = function(...args){
  1857. logger.success(...args);
  1858. };
  1859. return logger;
  1860. }
  1861. String.prototype.matches = function(rex){
  1862. if(!(rex instanceof RegExp))
  1863. return log("Not a regular Expression:", rex);
  1864. return rex.exec(this);
  1865. };
  1866.  
  1867. function mergeDeep(target, source, mutate=true){
  1868. let output = mutate ? target : Object.assign({}, target);
  1869. if(typeof target == "object"){
  1870. if(typeof source != "object")
  1871. source = {source};
  1872. Object.keys(source).forEach(key => {
  1873. if(typeof source[key] == "object"){
  1874. if(!(key in target))
  1875. Object.assign(output, { [key]: source[key] });
  1876. else
  1877. output[key] = mergeDeep(target[key], source[key]);
  1878. }else{
  1879. Object.assign(output, { [key]: source[key] });
  1880. }
  1881. });
  1882. }
  1883. return output;
  1884. }
  1885. function bpModal(style="light"){
  1886. var r = {};
  1887. r.messages = [];
  1888. r.count = 0;
  1889. const styles = {
  1890. light: {
  1891. colors: {
  1892. msg: {
  1893. border: "#666",
  1894. background: "#eee",
  1895. color: "#333"
  1896. },
  1897. error: {
  1898. border: "#a10",
  1899. background: "#ffcabf",
  1900. color: "#610"
  1901. },
  1902. warn: {
  1903. border: "#cc4",
  1904. background: "#fec",
  1905. color: "#880"
  1906. },
  1907. success: {
  1908. border: "#190",
  1909. background: "#bf9",
  1910. color: "#070"
  1911. },
  1912. modal: {
  1913. border: "#fff",
  1914. background: "#eee",
  1915. color: "#333"
  1916. },
  1917. modalClose: {
  1918. border: "#fff",
  1919. background: "#000",
  1920. color: "#fff"
  1921. }
  1922. }
  1923. },
  1924. dark: {
  1925. colors: {
  1926. msg: {
  1927. border: "#aaa",
  1928. background: "#222",
  1929. color: "#ddd"
  1930. },
  1931. error: {
  1932. border: "#722",
  1933. background: "#300",
  1934. color: "#c66"
  1935. },
  1936. warn: {
  1937. border: "#cc4",
  1938. background: "#330",
  1939. color: "#dd6"
  1940. },
  1941. success: {
  1942. border: "#190",
  1943. background: "#040",
  1944. color: "#6d6"
  1945. },
  1946. modal: {
  1947. border: "#333",
  1948. background: "#000",
  1949. color: "#ccc"
  1950. },
  1951. modalClose: {
  1952. border: "#fff",
  1953. background: "#000",
  1954. color: "#fff"
  1955. }
  1956. }
  1957. },
  1958. };
  1959. r.style = styles[style];
  1960. if(!r.style)
  1961. r.style = styles.light;
  1962. r.css = `#messageoverlays{
  1963. position:fixed;
  1964. top:20vh;
  1965. z-index:1000;
  1966. text-align:left;
  1967. margin: 0 auto;
  1968. left:50%;
  1969. transform: translate(-50%, 0px);
  1970. }
  1971.  
  1972. #messageoverlays>.table{
  1973. margin: 0px auto;
  1974. }
  1975.  
  1976. #messageoverlays .msg{
  1977. display:inline-block;
  1978. width:auto;
  1979. margin: 5px auto;
  1980. position:relative;
  1981. padding: 10px;
  1982. box-sizing: border-box;
  1983. border-radius: 5px;
  1984. }
  1985. #messageoverlays .msg{
  1986. border: 1px solid ${r.style.colors.msg.border};
  1987. background-color: ${r.style.colors.msg.background};
  1988. color: ${r.style.colors.msg.color};
  1989. }
  1990. #messageoverlays .msg.error{
  1991. border: 1px solid ${r.style.colors.error.border};
  1992. background-color: ${r.style.colors.error.background};
  1993. color: ${r.style.colors.error.color};
  1994. }
  1995. #messageoverlays .msg.warn{
  1996. border: 1px solid ${r.style.colors.warn.border};
  1997. background-color: ${r.style.colors.warn.background};
  1998. color: ${r.style.colors.warn.color};
  1999. }
  2000.  
  2001. #messageoverlays .msg.success{
  2002. border: 1px solid ${r.style.colors.success.border};
  2003. background-color: ${r.style.colors.success.background};
  2004. color: ${r.style.colors.success.color};
  2005. }
  2006.  
  2007. .closebutton{
  2008. font-weight: 900;
  2009. font-size: 12px;
  2010. cursor: pointer;
  2011. z-index: 20;
  2012. opacity: 0.75;
  2013. color: #fff;
  2014. background-color: #a10;
  2015. padding: 0 5px 1px;
  2016. border-radius: 100%;
  2017. position: absolute;
  2018. right: -5px;
  2019. top: -2px;
  2020. line-height: 16px;
  2021. }
  2022.  
  2023. .closebutton:hover,
  2024. .bpModback .modclose:hover{
  2025. opacity: 1;
  2026. }
  2027.  
  2028.  
  2029. .bpModback{
  2030. position:fixed;
  2031. width:100%;
  2032. height:100%;
  2033. display:table;
  2034. left:0;
  2035. top:0;
  2036. z-index:99000;
  2037. }
  2038. .bpModback.tint{
  2039. background-color:rgba(0,0,0,0.5);
  2040. }
  2041. .bpModback.nomodal{
  2042. display:block;
  2043. width: auto;
  2044. height: auto;
  2045. left: 50%;
  2046. top: 20px;
  2047. transform: translateX(-50%);
  2048. }
  2049. .bpModback .modcent{
  2050. display:table-cell;
  2051. vertical-align:middle;
  2052. height:100%;
  2053. max-height:100%;
  2054. min-height:100%;
  2055. }
  2056. .bpModback.nomodal .modcent{
  2057. height:auto;
  2058. max-height:auto;
  2059. }
  2060. .bpModback .modtable{
  2061. display:table;
  2062. margin:auto;
  2063. position:relative;
  2064. left:0;
  2065. }
  2066. .bpModback .modframe{
  2067. border-radius: 6px;
  2068. border:10px solid ${r.style.colors.modal.border};
  2069. display:block;
  2070. background-color: ${r.style.colors.modal.background};
  2071. box-shadow: 0 0 20px rgba(0,0,0,0.5);
  2072. max-height: 90vh!important;
  2073. max-width: 90vw!important;
  2074. overflow-y:auto;
  2075. }
  2076. .bpModback .modclose{
  2077. display:block;
  2078. background-color: ${r.style.colors.modalClose.background};
  2079. color: ${r.style.colors.modalClose.color};
  2080. opacity:0.7;
  2081. position:absolute;
  2082. right:-12px;
  2083. top:-12px;
  2084. -webkit-border-radius: 20px;
  2085. -moz-border-radius: 20px;
  2086. border-radius: 20px;
  2087. border:4px solid ${r.style.colors.modalClose.border};
  2088. font-weight:900;
  2089. font-size:12pt;
  2090. padding:0px 7px;
  2091. cursor:pointer;
  2092. z-index:400;
  2093. }
  2094. .bpModback .modbox{
  2095. position:relative;
  2096. display: table;
  2097. padding:20px 20px 10px;
  2098. color: ${r.style.colors.modal.color};
  2099. overflow: unset;
  2100. display:block;
  2101. text-align: left;
  2102. }
  2103. .bpModback .table{
  2104. display:table;
  2105. }
  2106. .bpModback .tr{
  2107. display:table-row;
  2108. }
  2109. .bpModback .td{
  2110. display: table-cell;
  2111. }
  2112. #watch #player{
  2113. height: 200px;
  2114. }`;
  2115. r.setup = function(){
  2116. // TODO: retrofill
  2117. if(typeof $ !== "function"){
  2118. return console.error("bpMenu: No jQuery '$'; can't continue");
  2119. // console.log("jQuery not available?\nTrying to insert & load...", typeof $);
  2120. // script = document.createElement("script");
  2121. // script.type = "text/javascript";
  2122. // script.onload = function(){
  2123. // r.setup();
  2124. // };
  2125. // script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js";
  2126. // head.appendChild(script);
  2127. // return;
  2128. }
  2129. // console.log("Actual setup with jQuery");
  2130. if($("head #bpModalStyle").length<=0){
  2131. $("head").append(`<style id="bpModalStyle">
  2132. ${r.css}
  2133. </style>`);
  2134. }
  2135. };
  2136. r.msg = function(instr, id="", modal=true, callback=null){
  2137. r.count++;
  2138. if(!id){
  2139. id = "bpmod" + r.count;
  2140. }
  2141. var noclose = false;
  2142. if(typeof(modal)=="string" && modal == "noclose"){
  2143. noclose = true;
  2144. modal = true;
  2145. }
  2146. var m = {
  2147. id : id,
  2148. msg: instr,
  2149. callback: callback,
  2150. modal: modal,
  2151. 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>"),
  2152. close: function(){
  2153. this.obj.remove();
  2154. delete r.messages[this.id];
  2155. if(this.callback)
  2156. this.callback(this);
  2157. }
  2158. };
  2159.  
  2160. $("body").append(m.obj);
  2161. $("#" + id + ":not('.noclose') .modcent").click(function(e){
  2162. if(e.target == this)
  2163. m.close(this);
  2164. });
  2165. $("#" + id + " .modclose").click(function(e){
  2166. m.close(this);
  2167. });
  2168. r.messages[id] = m;
  2169. return m;
  2170. };
  2171. r.close = function(el="all"){
  2172. if(el=="all"){
  2173. }
  2174. };
  2175. r.setup();
  2176. return r;
  2177. }
  2178. //if("undefined" === typeof bpModHelper){ // jshint ignore:line
  2179. // var bpModHelper = bpModal(); // jshint ignore:line
  2180. //}
  2181. function message(content, classname, id, expirein, closable){
  2182. expirein = typeof expirein !== 'undefined' ? expirein : 0;
  2183. if(closable===undefined)
  2184. closable = true;
  2185. var expires = expirein !== 0 ? true : false;
  2186. if (id === undefined || id === ""){
  2187. for (var i = 0; i < 512; i++){
  2188. if ($(document).find("#message-" + i)[0] !== undefined){} else {
  2189. this.id = "message-" + i;
  2190. break;
  2191. }
  2192. }
  2193. } else {
  2194. this.id = id;
  2195. }
  2196. var fid = this.id;
  2197. this.expire = function(){
  2198. if (expirein > 0){
  2199. if(this.extimer)
  2200. window.clearTimeout(this.extimer);
  2201. this.extimer = window.setTimeout(function(){
  2202. $("#" + fid).fadeOut(function(){
  2203. $("#" + fid).remove();
  2204. });
  2205. }, expirein);
  2206. }
  2207. };
  2208. 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>";
  2209. }
  2210.  
  2211. function overlaymessage(content, classname, id, expirein, closable){
  2212. expirein = typeof expirein !== 'undefined' ? expirein : 5000;
  2213. classname = classname || "hint";
  2214. id = id || "";
  2215. var curmes = new message(content, classname, id, expirein, closable);
  2216. //console.log(curmes);
  2217. if($("#messageoverlays").length<=0)
  2218. $("body").append("<div id='messageoverlays'></div>");
  2219. $("#messageoverlays").append(curmes.html);
  2220. $(".msg .closebutton").off("click").on("click", function(){
  2221. console.log("close", $(this).parent().parent());
  2222. $(this).parent().parent().fare();
  2223. });
  2224. curmes.expire();
  2225. }
  2226.  
  2227. function msg(content, classname, id, expirein){
  2228. overlaymessage(content, classname, id, expirein);
  2229. }
  2230.  
  2231. function msgbox(content, classname="", id=""){
  2232. if (id === undefined || id === ""){
  2233. for (var i = 0; i < 512; i++){
  2234. if ($(document).find("#message-" + i)[0] !== undefined){} else {
  2235. id = "message-" + i;
  2236. break;
  2237. }
  2238. }
  2239. }
  2240. return "<div id='" + id + "' class='msg " + classname + "'>" + content + "</div>";
  2241. }
  2242. String.prototype.rIndexOf = function(regex, startpos) {
  2243. var indexOf = this.substring(startpos || 0).search(regex);
  2244. return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
  2245. };
  2246. var regEsc = regEsc ? regEsc : function(str) {
  2247. if (typeof str != "string") {
  2248. console.warn("called regEsc with non-string:", str);
  2249. return str;
  2250. }
  2251. return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  2252. };
  2253.  
  2254. String.prototype.replaceAllInsensitive = function(str1, str2)
  2255. {
  2256. return this.replace(new RegExp(regEsc(str1), "gi"),(typeof str2 == "string")?str2.replace(/\$/g,"$$$$"):str2);
  2257. };
  2258. function scrollIntoView(object, offsetTop = 20, offsetLeft = 20){
  2259. object = $(object);
  2260. if(object.length<=0)
  2261. return;
  2262. object = object[0];
  2263. var offset = $(object).offset();
  2264. $('html, body').animate({
  2265. scrollTop: offset.top - offsetTop,
  2266. scrollLeft: offset.left - offsetLeft
  2267. });
  2268. object.scrollIntoView();
  2269. }
  2270. class elementSelector{
  2271. constructor(element=false, attributes=true){
  2272. this.tagName =
  2273. this.id =
  2274. this.className =
  2275. this.sibling =
  2276. "";
  2277. this._attribs = {};
  2278. if(element){
  2279. if(isJQuery(element)){
  2280. if(element.length<=0)
  2281. return;
  2282. element = element[0];
  2283. }
  2284. if(element instanceof HTMLElement){
  2285. var t = this;
  2286. this.element = element;
  2287. this.tagName = element.tagName.toLowerCase();
  2288. if(element.id)
  2289. this.id = "#" + element.id;
  2290. if(element.className){
  2291. this.className = "." + element.className.trim().replace(/\s+/g, ".");
  2292. }
  2293. if(attributes)
  2294. for(let attr of element.attributes){
  2295. if(["id", "class"].includes(attr.name))
  2296. continue;
  2297. if(typeof attributes == "string")
  2298. attributes = [attributes];
  2299. if(attributes instanceof Array && !attributes.includes(attr.name))
  2300. continue;
  2301. if(attr.value)
  2302. this._attribs[attr.name] = attr.value;
  2303. }
  2304. var p = element.parentElement;
  2305. if(p && p.childElementCount>1){
  2306. var sibs = p.querySelectorAll(":scope > " + this.tagName);
  2307. if(sibs && sibs.length>1){
  2308. for(let i = 0; i<sibs.length; i++){
  2309. let sib = sibs[i];
  2310. if(sib == element){
  2311. // console.log("found in siblings:", el);
  2312. if(i==0)
  2313. t.sibling = ":first-of-type";
  2314. else if(i==sibs.length-1)
  2315. t.sibling = ":last-of-type";
  2316. else
  2317. t.sibling = ":nth-of-type(" + (i+1) + ")";
  2318. return false;
  2319. }
  2320. // console.log("not same:", sib, el);
  2321. }
  2322. }
  2323. }
  2324. }
  2325. }
  2326. }
  2327. attribs(filter=false){
  2328. var r = "";
  2329. if(typeof filter == "string")
  2330. filter = [filter];
  2331. for(let k in this._attribs){
  2332. if(filter instanceof Array && !filter.includes(k))
  2333. continue;
  2334. let v = this._attribs[k];
  2335. r += `[${k}="${v}"]`;
  2336. }
  2337. return r;
  2338. }
  2339. get attributes(){
  2340. return this.attribs();
  2341. }
  2342. clone(){
  2343. var r = new elementSelector();
  2344. Object.assign(r, this);
  2345. r._attribs = {};
  2346. for(let k in this._attribs)
  2347. r._attribs[k] = this._attribs[k];
  2348. return r;
  2349. }
  2350. toString(includeAttribs = true, includeSibling = true){
  2351. return this.tagName + this.id + this.className + (includeAttribs?this.attribs(includeAttribs):"") + (includeSibling?this.sibling:"");
  2352. }
  2353. countMatches(descendants = null, includeAttribs = true, includeSibling = true){
  2354. if(descendants)
  2355. return descendants.add(this, true).toString(includeAttribs, includeSibling);
  2356. var s = this.toString(includeAttribs, includeSibling);
  2357. if(s)
  2358. return document.querySelectorAll(s).length;
  2359. return 0;
  2360. }
  2361. }
  2362. class elementSelectorChain{
  2363. constructor(elements = false, reverse = false, attributes=true){
  2364. this.selectors = [];
  2365. if(elements){
  2366. if(isJQuery(elements))
  2367. elements = elements.get();
  2368. if(elements instanceof Array){
  2369. if(reverse)
  2370. elements = elements.reverse();
  2371. for(let e of elements){
  2372. if(!e){
  2373. this.add(null);
  2374. continue;
  2375. }
  2376. if(e instanceof elementSelector)
  2377. this.add(e);
  2378. else
  2379. this.add(new elementSelector(e, attributes));
  2380. }
  2381. }
  2382. else if(elements instanceof HTMLElement)
  2383. this.add(new elementSelector(elements, attributes));
  2384. else console.warn("elementSelectorChain: Could not construct with", elements, ", starting empty");
  2385. }
  2386. }
  2387. add(selector, toStart=false){
  2388. if(toStart){
  2389. this.selectors = [selector, ...this.selectors];
  2390. return this;
  2391. }
  2392. this.selectors.push(selector);
  2393. return this;
  2394. }
  2395. each(func, reverse=false){
  2396. var l = this.selectors.length-1;
  2397. for(let i = 0; i<=l; i++){
  2398. let j = reverse?l-i:i;
  2399. let e = this.selectors[j];
  2400. let r = func(e, j);
  2401. if(r===false)
  2402. break;
  2403. if(r === null || r instanceof elementSelector)
  2404. this.selectors[j] = r;
  2405. }
  2406. }
  2407. length(includeEmpty=false){
  2408. if(includeEmpty)
  2409. return this.selectors.length;
  2410. var c = 0;
  2411. for(let s of this.selectors)
  2412. if(s) c++;
  2413. return c;
  2414. }
  2415. get(ix){
  2416. return this.selectors[ix];
  2417. }
  2418. getElements(){
  2419. var r = [];
  2420. var s = this.toString();
  2421. if(s)
  2422. r = document.querySelectorAll(s);
  2423. return r;
  2424. }
  2425. clone(){
  2426. var r = new elementSelectorChain();
  2427. this.each(function(s){
  2428. if(!s)
  2429. r.add(null);
  2430. else
  2431. r.add(s.clone());
  2432. });
  2433. return r;
  2434. }
  2435. toString(includeAttribs = true, includeSibling = true){
  2436. var r = "";
  2437. var isDirect = true;
  2438. var isFirst = true;
  2439. for(let e of this.selectors){
  2440. if(!e){
  2441. isDirect = false;
  2442. continue;
  2443. }
  2444. r += (isFirst?"":(isDirect?" > ":" ")) + e.toString(includeAttribs, includeSibling);
  2445. isDirect = true;
  2446. isFirst = false;
  2447. }
  2448. return r;
  2449. }
  2450. countMatches(includeAttribs = true, includeSibling = true){
  2451. var s = this.toString(includeAttribs, includeSibling);
  2452. if(s)
  2453. return document.querySelectorAll(s).length;
  2454. return 0;
  2455. }
  2456. }
  2457.  
  2458. /**
  2459. * @name getSelector
  2460. * @description Get a matching CSS selector for the given HTML Element
  2461. * @param {(HTMLElement|jQuery)} element - The object (instance of HTMLElement or jQuery/$) for which to compose the selector
  2462. * @param {boolean} [minimal=true] - Try to trim selectors that don&apos;t narrow down the search on this page? (default: true)
  2463. * @param {(boolean|string|string[]|"auto")} [attributes="auto"] - Should attributes be included? (default: "auto")
  2464. * - string/string[]: Filter by attribute name[list]
  2465. * - \"auto\": include if it narrows down the search
  2466. * @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)
  2467. * @returns {string} CSS Selector
  2468. */
  2469. function getSelector(element, minimal = true, attributes = "auto", preferTopDown = true){
  2470. return traverseAncestryForSelectors(element, minimal, attributes, preferTopDown).toString();
  2471. }
  2472. function getSelectors(elements, minimal = true, attributes = "auto", preferTopDown = true){
  2473. var r = [];
  2474. for(let element of elements)
  2475. r.push(traverseAncestryForSelectors(element, minimal, attributes, preferTopDown).toString());
  2476. return r;
  2477. }
  2478.  
  2479. function getSelectorChoices(element, attributes = "auto"){
  2480. var r = [];
  2481. r.push(traverseAncestryForSelectors(element, true, attributes, true).toString());
  2482. r.push(traverseAncestryForSelectors(element, true, attributes, false).toString());
  2483. r.push(traverseAncestryForSelectors(element, false, attributes).toString());
  2484. return r;
  2485. }
  2486.  
  2487. function traverseAncestryForSelectors(element, minimal = true, attributes = "auto", preferTopDown = true){
  2488. if(!element)
  2489. throw new Error("traverseAncestryForSelectors: Not a valid element");
  2490. if(isJQuery(element)){
  2491. if(element.length<=0)
  2492. throw new Error("traverseAncestryForSelectors: No element in jQuery object");
  2493. element = element[0];
  2494. }
  2495. var ancestry = [];
  2496. if(typeof $ == "function" && typeof $.prototype == "object" && typeof $(element) == "object" && typeof $(element).parents == "function"){
  2497. ancestry = $(element).add($(element).parents());
  2498. }
  2499. else{
  2500. var p = element;
  2501. while(p && p != document){
  2502. ancestry.push(p);
  2503. p = p.parentElement;
  2504. }
  2505. ancestry = ancestry.reverse();
  2506. }
  2507. var fullChain = new elementSelectorChain(ancestry, false, attributes);
  2508. var tmpChain = fullChain.clone();
  2509. var chain = new elementSelectorChain();
  2510. var cs = fullChain.toString(attributes);
  2511. if(!cs)
  2512. throw new Error("Selector empty");
  2513. var bestCount = document.querySelectorAll(cs).length;
  2514. if(bestCount<=0){
  2515. console.warn("Selector chain too restrictive/broken?", cs, fullChain);
  2516. }
  2517. // console.log("Complete chain:", cs, fullChain, bestCount==1?"UNIQUE":bestCount);
  2518. function applyIfViable(tmpSelector, ix){
  2519. if(fullChain.selectors[ix] === null || fullChain.selectors[ix] == tmpSelector)
  2520. return fullChain.selectors[ix];
  2521. if(tmpSelector && (tmpSelector.tagName + tmpSelector.id + tmpSelector.className) == "")
  2522. return fullChain.selectors[ix];
  2523. tmpChain.selectors[ix] = tmpSelector;
  2524. let els = tmpChain.getElements();
  2525. // console.log(els.length, bestCount, els.is(element), els, element);
  2526. if(els.length>bestCount || els[0] != element){
  2527. tmpChain.selectors[ix] = fullChain.selectors[ix].clone();
  2528. }
  2529. else{
  2530. if(tmpSelector)
  2531. fullChain.selectors[ix] = tmpSelector.clone();
  2532. else
  2533. fullChain.selectors[ix] = null;
  2534. // console.log("set to ", tmpSelector, fullChain.selectors[ix]);
  2535. }
  2536. return fullChain.selectors[ix];
  2537. }
  2538. // console.log("going DOWN");
  2539. fullChain.each(function(sel, ix){
  2540. // console.log(ix, sel.toString(), tmpChain.selectors[ix]);
  2541. if(!sel || !sel.sibling)
  2542. return;
  2543. var c = sel.clone();
  2544. c.sibling = "";
  2545. // console.log("before sib check", ix, c, tmpChain.selectors[ix]);
  2546. applyIfViable(c, ix);
  2547. // console.log("after sib check", tmpChain.selectors[ix]);
  2548. });
  2549. // console.log("going UP", fullChain.toString());
  2550. if(!minimal)
  2551. return fullChain;
  2552. fullChain.each(function(sel, ix){
  2553. // console.log(ix, sel.toString());
  2554. var c = sel;
  2555. if(minimal && ix<=fullChain.length()-1)
  2556. c = applyIfViable(null, ix);
  2557. if(c){
  2558. c = c.clone();
  2559. if(attributes=="auto")
  2560. for(let k in c._attribs){
  2561. // console.log("before attrib remove", k, c._attribs);
  2562. delete c._attribs[k];
  2563. // console.log("before attrib check", k, c._attribs);
  2564. c = applyIfViable(c, ix).clone();
  2565. // console.log("after attrib check", k, c._attribs);
  2566. }
  2567. for(let k of ["tagName", "className"]){
  2568. // console.log("before attrib remove", ix, k, c);
  2569. c[k] = "";
  2570. // console.log("before attrib check", k, c);
  2571. c = applyIfViable(c, ix).clone();
  2572. // console.log("after attrib check", k, c);
  2573. }
  2574. }
  2575. else{
  2576. // console.log("skipped", sel.toString());
  2577. }
  2578. chain.add(c, preferTopDown);
  2579. //console.log("currently at", s);
  2580. if(minimal && c && document.querySelector(chain.toString()) == element)
  2581. return false;
  2582. }, preferTopDown);
  2583. return chain;
  2584. }
  2585. // TODO: option to filter attribs by specific values also, not just keys
  2586. /* globals isElement, isNativeFunction, uneval */
  2587. function stringify(obj, forHTML, onlyOwnProperties, completeFunctions, level, maxLevel, skipEmpty){
  2588. if(!level) level = 0;
  2589. var r = "";
  2590. if(obj===undefined) r = "[undefined]";
  2591. else if(obj === null) r = "[null]";
  2592. else if(obj === false) r = "FALSE";
  2593. else if(obj === true) r = "TRUE";
  2594. else if(obj==="") r = "[empty]";
  2595. else if(typeof obj == "object"){
  2596. var isDOMElement = isElement(obj);
  2597. if(onlyOwnProperties === undefined) onlyOwnProperties = true;
  2598. if(completeFunctions === undefined) completeFunctions = false;
  2599. if(maxLevel === undefined) maxLevel = 5;
  2600. if(skipEmpty === undefined) skipEmpty = false;
  2601. r = "[object] ";
  2602. var level_padding = "";
  2603. var padString = " ";
  2604. for(var j = 0; j < level; j++) level_padding += padString;
  2605. if(isDOMElement){
  2606. r = "[DOMElement " + obj.nodeName + "] ";
  2607. skipEmpty = true;
  2608. completeFunctions = false;
  2609. }
  2610. if(level<maxLevel){
  2611. r += "{\n";
  2612. if(isDOMElement){
  2613. r += level_padding + padString + "HTML => " + obj.outerHTML.replace(/\r?\n/g, "\\n").replace(/\s+/g, " ") + "\n";
  2614. }
  2615. for(var item in obj){
  2616. try{
  2617. var value = obj[item];
  2618. if(onlyOwnProperties && obj.hasOwnProperty && !obj.hasOwnProperty(item) || isNativeFunction(value) || skipEmpty && (value===undefined || value === null || value===""))
  2619. continue;
  2620. if(typeof(value) == 'object'){
  2621. r += level_padding + padString + "'" + item + "' => ";
  2622. r += stringify(value, forHTML, onlyOwnProperties, completeFunctions, level+1, maxLevel, skipEmpty) + "\n";
  2623. }else if(typeof(value) == 'undefined'){
  2624. r += level_padding + padString + "'" + item + "' => [undefined]\n";
  2625. }else{
  2626. if(typeof(value.toString)=="function")
  2627. value = value.toString();
  2628. if(!completeFunctions){
  2629. let m = value.match(/function\s*\(([^\)]*)\)\s*\{/i);
  2630. if(m)
  2631. value = "function(" + m[1] + ")";
  2632. }
  2633. r += level_padding + padString + "'" + item + ("' => \"" + value).replace(/\r?\n/g, "\\n").replace(/\s+/g, " ") + "\"\n";
  2634. }
  2635. }catch(e){
  2636. console.log(e);
  2637. }
  2638. }
  2639. r += level_padding + "}";
  2640. }else
  2641. r += "[Max depth of " + maxLevel + " exceeded]";
  2642. }
  2643. else if(typeof obj == "function"){
  2644. if(typeof(obj.toString)=="function")
  2645. r = obj.toString();
  2646. else
  2647. r = uneval(obj);
  2648. if(!completeFunctions){
  2649. let m = r.match(/function\s*\(([^\)]*)\)\s*\{/i);
  2650. if(m)
  2651. r = "function(" + m[1] + ")";
  2652. }
  2653. }
  2654. else
  2655. r = obj + "";
  2656. if(level===0){
  2657. if(!!forHTML){
  2658. r = r.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  2659. r = "<pre>" + r + "</pre>";
  2660. }
  2661. }
  2662. return r;
  2663. }
  2664. function isNode(o){
  2665. return (
  2666. typeof Node === "object" ? o instanceof Node :
  2667. o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
  2668. );
  2669. }
  2670. /* globals HTMLDocument */
  2671. function isElement(o){
  2672. return (
  2673. ((typeof HTMLElement === "object" && o instanceof HTMLElement) || (typeof Element === "object" && o instanceof Element) || (typeof HTMLDocument === "object" && o instanceof HTMLDocument))? true : //DOM2
  2674. o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
  2675. );
  2676. }
  2677. function stringifyWithFuncs(val, withTabs = true){
  2678. return JSON.stringify(val, function(key, value){
  2679. if (typeof value === 'function') {
  2680. return value.toString();
  2681. }
  2682. return value;
  2683. }, withTabs?"\t":" ");
  2684. }
  2685. function toText(str, maxBreaks=2){
  2686. // if($ === undefined){
  2687. // $ = jQuery = require( "jquery" )(new JSDOM("").window);
  2688. // }
  2689. var hack = "__break god dammit__";
  2690. str = str.replace(/(<(?:br|hr) ?\/?>|<\/(?:p|li|div|td|h\d)>)/gi, hack + "$1");
  2691. str = $("<div/>").append(str).text();
  2692. var rex = new RegExp(hack, "gi");
  2693. str = str.replace(rex, "\n");
  2694. rex = new RegExp("(\\n{" + maxBreaks + "})\\n+", "g");
  2695. str = str.replace(rex, "$1");
  2696. return str.trim();
  2697. }
  2698. function toVarName(str){
  2699. return str
  2700. .replace(/\s+(\w)/g, function(_, a){
  2701. return a.toUpperCase();
  2702. })
  2703. .replace(/[^a-zA-Z0-9]/g, "_")
  2704. .replace(/^([0-9])/, "_$1");
  2705. }
  2706. function trim(s, what="\\s"){
  2707. var rex = new RegExp("^(?:[" + what + "])*((?:[\\r\\n]|.)*?)(?:[" + what + "])*$");
  2708. var m = s.match(rex);
  2709. // log(m);
  2710. if(m !== null && m.length>=2)
  2711. return m[1];
  2712. return "";
  2713. }
  2714. function varToPretty(str){
  2715. 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();});
  2716. }
  2717. class eleWaiter{
  2718. constructor(sel, cb, cbFail=null, findIn=null, delay=500, maxTries=50, alwaysOn=false, autoStart=true, debug = false, logFunc = null){
  2719. this.sel = "";
  2720. this.cb = null;
  2721. this.cbFail = null;
  2722. this.findIn = null;
  2723. this.delay = 500;
  2724. this.maxTries = 50;
  2725. this.alwaysOn = false;
  2726. this.autoStart = true;
  2727. this.debug = false;
  2728. this.logFunc = null;
  2729.  
  2730. this.__running = false;
  2731. this.__tries = 0;
  2732. this.__timer = 0;
  2733. this.__jqo = {};
  2734.  
  2735. if(typeof sel == "object" && !(sel instanceof Array)){ // 2022-04-16 : Now allowing array of selectors
  2736. // log("got object");
  2737. Object.assign(this, sel);
  2738. }
  2739. else{
  2740. this.sel = sel;
  2741. this.cb = cb;
  2742. if(cbFail!== undefined || cbFail!== null)
  2743. this.cbFail = cbFail;
  2744. if(findIn)
  2745. this.findIn = findIn;
  2746. this.delay = delay;
  2747. this.maxTries = maxTries;
  2748. this.alwaysOn = alwaysOn;
  2749. this.autoStart = autoStart;
  2750. this.debug = debug;
  2751. this.logFunc = logFunc;
  2752. }
  2753. if(!(this.sel instanceof Array)){ // 2022-04-16 : Now allowing array of selectors
  2754. this.sel = [this.sel];
  2755. }
  2756. if(this.debug){
  2757. if(typeof this.debug == "string"){
  2758. this.debug = {
  2759. prefix: this.debug,
  2760. level: 1
  2761. };
  2762. }
  2763. else if(typeof this.debug == "number"){
  2764. this.debug = {
  2765. prefix: "",
  2766. level: this.debug
  2767. };
  2768. }
  2769. else if(typeof this.debug == "object"){
  2770. if(!this.debug.prefix)
  2771. this.debug.prefix = "";
  2772. if(!this.debug.level)
  2773. this.debug.level = 1;
  2774. }
  2775. else{
  2776. this.debug = {
  2777. prefix: "",
  2778. level: 1
  2779. };
  2780. }
  2781. }
  2782. if(!this.logFunc){
  2783. var prefix = "";
  2784. if(this.debug)
  2785. prefix = this.debug.prefix;
  2786. if(typeof BPLogger != "undefined"){
  2787. var logger = new BPLogger(prefix ? prefix + " EleWaiter" : "EleWaiter");
  2788. this.logFunc = logger.log.bind(logger);
  2789. // this.debug.prefix = false;
  2790. }
  2791. else{
  2792. this.logFunc = function(...args){
  2793. console.log("EleWaiter:", ...args);
  2794. };
  2795. }
  2796. }
  2797. this.log(this, 3);
  2798. if(this.autoStart)
  2799. this.__wait();
  2800. }
  2801. log(...args){
  2802. if(!this.debug)
  2803. return;
  2804. if(typeof args == "object" && args instanceof Array && args.length>=2 && typeof args[args.length-1] == "number"){
  2805. var level = args[args.length-1];
  2806. if(level>this.debug.level){
  2807. return;
  2808. }
  2809. args.pop();
  2810. }
  2811. this.logFunc(...args);
  2812. }
  2813.  
  2814. start(){
  2815. if(!this.__running){
  2816. this.log("Start waiting", this.findIn, this.sel, 1);
  2817. this.__wait();
  2818. }
  2819. }
  2820. stop(){
  2821. clearTimeout(this.__timer);
  2822. this.__running = false;
  2823. }
  2824.  
  2825. __wait(){
  2826. if(!this.findIn || this.findIn == "document"){
  2827. if(!!document)
  2828. this.findIn = document;
  2829. else
  2830. this.findIn = $(":root");
  2831. }
  2832. this.__running = true;
  2833. if(this.maxTries!=-1)
  2834. this.__tries++;
  2835. var triesLeft = this.alwaysOn?1:(this.maxTries - this.__tries);
  2836. this.log("tries left:", triesLeft, 3);
  2837. this.__jqo = $();
  2838. for(let sel of this.sel){
  2839. if(typeof sel == "function"){ // 2022-07-11: predicate style
  2840. this.log("sel is func:", this.sel, 3);
  2841. jqo = $(this.findIn);
  2842. var res = sel(jqo);
  2843. if(!res){
  2844. if(!this.alwaysOn)
  2845. this.log("Not true:", sel.toString(), "for", this.findIn, 3);
  2846. if(triesLeft!==0){
  2847. this.__timer = setTimeout(function(){this.__wait();}.bind(this), this.delay);
  2848. if(this.alwaysOn)
  2849. this.__result(false);
  2850. }
  2851. else
  2852. this.__result(false);
  2853. return;
  2854. }
  2855. else{
  2856. this.__jqo = this.__jqo.add(res);
  2857. this.log("Found something, is now:", this.__jqo, 3);
  2858. }
  2859. continue;
  2860. }
  2861. var jqo = $(this.findIn).find(sel);
  2862.  
  2863. if(jqo.length<=0){
  2864. if(!this.alwaysOn)
  2865. this.log("Not found: " + sel, "in", this.findIn, 3);
  2866. if(triesLeft!==0){
  2867. this.__timer = setTimeout(function(){this.__wait();}.bind(this), this.delay);
  2868. if(this.alwaysOn)
  2869. this.__result(false);
  2870. }
  2871. else
  2872. this.__result(false);
  2873. return;
  2874. }
  2875. else{
  2876. this.__jqo = this.__jqo.add(jqo);
  2877. this.log("Found something, is now:", this.__jqo, 3);
  2878. }
  2879. }
  2880. this.__result(this.__jqo);
  2881.  
  2882. if(this.alwaysOn){
  2883. this.log("Always on, repeat", 3);
  2884. this.__timer = setTimeout(function(){this.__wait();}.bind(this), this.delay);
  2885. }
  2886. }
  2887. __result(success=false){
  2888. if(!this.alwaysOn){
  2889. this.__running = false;
  2890. this.log("Result:", success, 2);
  2891. }else if(this.debug.level>2)
  2892. this.log("Result:", success, 1);
  2893. if(success){
  2894. if(this.cb!==undefined && typeof this.cb == "function")
  2895. this.cb(this.__jqo);
  2896. else
  2897. console.log("Warning: callback cb not function", this.cb);
  2898. }
  2899. else{
  2900. if(this.cbFail!==undefined && typeof this.cbFail == "function")
  2901. this.cbFail(this.__jqo);
  2902. }
  2903. }
  2904. }
  2905. if("undefined" === typeof eleWaiters){ // jshint ignore:line
  2906. var eleWaiters ={}; // jshint ignore:line
  2907. }
  2908.  
  2909. function waitFor(sel, cb, cbFail=null, findIn="document", delay=500, maxTries=50, alwaysOn=false, debug = false){ // 2021-01-29
  2910. return new eleWaiter(sel, cb, cbFail, findIn, delay, maxTries, alwaysOn, true, debug);
  2911. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址