billy-herrington-utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ ### _daddy told us not to be ashamed of our utils_
2
+ ![](https://i.imgur.com/wwfRj0R.jpeg)
3
+
4
+ * stringToWords
5
+ * sanitizeStr
6
+ * timeToSeconds
7
+ * parseCSSUrl
8
+ * parseDataParams
9
+ * parseIntegerOr
10
+ * Observer
11
+ * LazyImgLoader
12
+ * circularShift
13
+ * MOBILE_UA
14
+ * fetchHtml
15
+ * fetchText
16
+ * fetchWith
17
+ * objectToFormData
18
+ * Tick
19
+ * listenEvents
20
+ * parseDom
21
+ * copyAttributes
22
+ * downloader
23
+ * findNextSibling
24
+ * getAllUniqueParents
25
+ * replaceElementTag
26
+ * waitForElementExists
27
+ * watchDomChangesWithThrottle
28
+ * watchElementChildrenCount
29
+ * isMob
30
+ * computeAsyncOneAtTime
31
+ * SyncPull
32
+ * wait
33
+ * chunks
34
+ * range
package/biome.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3
+ "organizeImports": {
4
+ "enabled": true
5
+ },
6
+ "formatter": {
7
+ "enabled": true,
8
+ "indentWidth": 2,
9
+ "indentStyle": "space",
10
+ "lineWidth": 100,
11
+ "lineEnding": "lf"
12
+ },
13
+ "javascript": {
14
+ "formatter": {
15
+ "quoteStyle": "single",
16
+ "semicolons": "always"
17
+ }
18
+ },
19
+ "linter": {
20
+ "enabled": true,
21
+ "rules": {
22
+ "recommended": true,
23
+ "style": {
24
+ "useNumberNamespace": "off"
25
+ },
26
+ "complexity": {
27
+ "noStaticOnlyClass": "off",
28
+ "noForEach": "off"
29
+ },
30
+ "suspicious": {
31
+ "noRedundantUseStrict": "off"
32
+ }
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,242 @@
1
+ var b = Object.defineProperty;
2
+ var d = (e, t, r) => t in e ? b(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
3
+ var o = (e, t, r) => d(e, typeof t != "symbol" ? t + "" : t, r);
4
+ function I(e) {
5
+ return e.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t);
6
+ }
7
+ function A(e) {
8
+ return (e == null ? void 0 : e.replace(/\n|\t/, " ").replace(/ {2,}/, " ").trim().toLowerCase()) || "";
9
+ }
10
+ function E(e) {
11
+ return ((e == null ? void 0 : e.match(/\d+/gm)) || [0]).reverse().map((t, r) => parseInt(t) * 60 ** r).reduce((t, r) => t + r);
12
+ }
13
+ function T(e, t) {
14
+ return Number.isInteger(parseInt(e)) ? parseInt(e) : t;
15
+ }
16
+ function L(e) {
17
+ const t = e.split(";").flatMap((r) => {
18
+ const n = r.match(/([\+\w+]+):(\w+)?/), i = n == null ? void 0 : n[2];
19
+ if (i) return n[1].split("+").map((s) => ({ [s]: i }));
20
+ }).filter((r) => r);
21
+ return Object.assign({}, ...t);
22
+ }
23
+ function O(e) {
24
+ return e.replace(/url\("|\"\).*/g, "");
25
+ }
26
+ class l {
27
+ constructor(t) {
28
+ o(this, "observer");
29
+ this.callback = t, this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
30
+ }
31
+ observe(t) {
32
+ this.observer.observe(t);
33
+ }
34
+ throttle(t, r) {
35
+ this.observer.unobserve(t), setTimeout(() => this.observer.observe(t), r);
36
+ }
37
+ handleIntersection(t) {
38
+ for (const r of t)
39
+ r.isIntersecting && this.callback(r.target);
40
+ }
41
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
42
+ static observeWhile(t, r, n) {
43
+ const i = new l(async (s) => {
44
+ await r() && i.throttle(s, n);
45
+ });
46
+ return i.observe(t), i;
47
+ }
48
+ }
49
+ class u {
50
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
51
+ constructor(t, r = "data-lazy-load", n = !0) {
52
+ o(this, "lazyImgObserver");
53
+ o(this, "delazify", (t) => {
54
+ this.lazyImgObserver.observer.unobserve(t), t.src = t.getAttribute(this.attributeName), this.removeTagAfter && t.removeAttribute(this.attributeName);
55
+ });
56
+ this.attributeName = r, this.removeTagAfter = n, this.lazyImgObserver = new l((i) => {
57
+ t(i, this.delazify);
58
+ });
59
+ }
60
+ lazify(t, r, n) {
61
+ !r || !n || (r.setAttribute(this.attributeName, n), r.src = "", this.lazyImgObserver.observe(r));
62
+ }
63
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
64
+ static create(t) {
65
+ return new u((n, i) => {
66
+ t(n) && i(n);
67
+ });
68
+ }
69
+ }
70
+ function M(e, t = 6, r = 1) {
71
+ return (e + r) % t || t;
72
+ }
73
+ function h(e) {
74
+ const t = new DOMParser().parseFromString(e, "text/html").body;
75
+ return t.children.length > 1 ? t : t.firstElementChild;
76
+ }
77
+ function m(e, t) {
78
+ for (const r of t.attributes)
79
+ r.nodeValue && e.setAttribute(r.nodeName, r.nodeValue);
80
+ }
81
+ function S(e, t) {
82
+ var n;
83
+ const r = document.createElement(t);
84
+ return m(r, e), r.innerHTML = e.innerHTML, (n = e.parentNode) == null || n.replaceChild(r, e), r;
85
+ }
86
+ function z(e) {
87
+ return Array.from(e).reduce((t, r) => (r.parentElement && t.includes(r.parentElement) && t.push(r.parentElement), t), []);
88
+ }
89
+ function p(e) {
90
+ return e.nextElementSibling ? e.nextElementSibling : e.parentElement ? p(e.parentElement) : null;
91
+ }
92
+ function v(e, t, r) {
93
+ const n = new MutationObserver((i) => {
94
+ const s = e.querySelector(t);
95
+ s && (n.disconnect(), r(s));
96
+ });
97
+ n.observe(document.body, { childList: !0, subtree: !0 });
98
+ }
99
+ function F(e, t) {
100
+ let r = e.children.length;
101
+ new MutationObserver((i, s) => {
102
+ for (const a of i)
103
+ a.type === "childList" && r !== e.children.length && (r = e.children.length, t(s, r));
104
+ }).observe(e, { childList: !0 });
105
+ }
106
+ function P(e, t, r = 1e3, n = { childList: !0, subtree: !0, attributes: !0 }) {
107
+ let i, s;
108
+ new MutationObserver((y, w) => {
109
+ const c = Date.now();
110
+ i && c - i < r && s && clearTimeout(s), s = setTimeout(t, r), i = c;
111
+ }).observe(e, n);
112
+ }
113
+ function x(e = { append: "", after: "", button: "", cbBefore: () => {
114
+ } }) {
115
+ var r, n;
116
+ const t = h(e.button);
117
+ e.append && ((r = document.querySelector(e.append)) == null || r.append(t)), e.after && ((n = document.querySelector(e.after)) == null || n.after(t)), t.addEventListener("click", (i) => {
118
+ i.preventDefault(), e.cbBefore && e.cbBefore(), v(document.body, "video", (s) => {
119
+ window.location.href = s.getAttribute("src");
120
+ });
121
+ });
122
+ }
123
+ const g = [
124
+ "Mozilla/5.0 (Linux; Android 10; K)",
125
+ "AppleWebKit/537.36 (KHTML, like Gecko)",
126
+ "Chrome/114.0.0.0 Mobile Safari/537.36"
127
+ ].join(" ");
128
+ function f(e, t = { html: !1, mobile: !1 }) {
129
+ const r = {};
130
+ return t.mobile && Object.assign(r, { headers: new Headers({ "User-Agent": g }) }), fetch(e, r).then((n) => n.text()).then((n) => t.html ? h(n) : n);
131
+ }
132
+ const C = (e) => f(e, { html: !0 }), D = (e) => f(e);
133
+ function H(e) {
134
+ const t = new FormData();
135
+ return Object.entries(e).forEach(([r, n]) => t.append(r, n)), t;
136
+ }
137
+ function N(e, t, r) {
138
+ for (const n of t)
139
+ e.addEventListener(n, r, !0);
140
+ }
141
+ class j {
142
+ constructor(t, r = !0) {
143
+ o(this, "tick");
144
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
145
+ o(this, "callbackFinal");
146
+ this.delay = t, this.startImmediate = r, this.tick = null, this.delay = t, this.startImmediate = r;
147
+ }
148
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
149
+ start(t, r = null) {
150
+ this.stop(), this.callbackFinal = r, this.startImmediate && t(), this.tick = setInterval(t, this.delay);
151
+ }
152
+ stop() {
153
+ this.tick !== null && (clearInterval(this.tick), this.tick = null), this.callbackFinal && (this.callbackFinal(), this.callbackFinal = null);
154
+ }
155
+ }
156
+ function q() {
157
+ return /iPhone|Android/i.test(navigator.userAgent);
158
+ }
159
+ async function W(e) {
160
+ const t = [];
161
+ for await (const r of e)
162
+ t.push(await r());
163
+ return t;
164
+ }
165
+ function B(e) {
166
+ return new Promise((t) => setTimeout(t, e));
167
+ }
168
+ class U {
169
+ constructor() {
170
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
171
+ o(this, "pull", []);
172
+ o(this, "lock", !1);
173
+ }
174
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
175
+ getHighPriorityFirst(t = 0) {
176
+ if (t > 3 || this.pull.length === 0) return;
177
+ const r = this.pull.findIndex((n) => n.p === t);
178
+ if (r >= 0) {
179
+ const n = this.pull[r].v;
180
+ return this.pull = this.pull.slice(0, r).concat(this.pull.slice(r + 1)), n;
181
+ }
182
+ return this.getHighPriorityFirst(t + 1);
183
+ }
184
+ *pullGenerator() {
185
+ for (; this.pull.length > 0; )
186
+ yield this.getHighPriorityFirst();
187
+ }
188
+ async processPull() {
189
+ if (!this.lock) {
190
+ this.lock = !0;
191
+ for await (const t of this.pullGenerator())
192
+ await t();
193
+ this.lock = !1;
194
+ }
195
+ }
196
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
197
+ push(t) {
198
+ this.pull.push(t), this.processPull();
199
+ }
200
+ }
201
+ function _(e, t) {
202
+ const r = [];
203
+ for (let n = 0; n < e.length; n += t)
204
+ r.push(e.slice(n, n + t));
205
+ return r;
206
+ }
207
+ function G(e, t = 1) {
208
+ return [...Array(e).keys()].map((r) => r + t);
209
+ }
210
+ export {
211
+ u as LazyImgLoader,
212
+ g as MOBILE_UA,
213
+ l as Observer,
214
+ U as SyncPull,
215
+ j as Tick,
216
+ _ as chunks,
217
+ M as circularShift,
218
+ W as computeAsyncOneAtTime,
219
+ m as copyAttributes,
220
+ x as downloader,
221
+ C as fetchHtml,
222
+ D as fetchText,
223
+ f as fetchWith,
224
+ p as findNextSibling,
225
+ z as getAllUniqueParents,
226
+ q as isMob,
227
+ N as listenEvents,
228
+ H as objectToFormData,
229
+ O as parseCSSUrl,
230
+ L as parseDataParams,
231
+ h as parseDom,
232
+ T as parseIntegerOr,
233
+ G as range,
234
+ S as replaceElementTag,
235
+ A as sanitizeStr,
236
+ I as stringToWords,
237
+ E as timeToSeconds,
238
+ B as wait,
239
+ v as waitForElementExists,
240
+ P as watchDomChangesWithThrottle,
241
+ F as watchElementChildrenCount
242
+ };
@@ -0,0 +1 @@
1
+ (function(i,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(i=typeof globalThis<"u"?globalThis:i||self,l(i.bhutils={}))})(this,function(i){"use strict";var B=Object.defineProperty;var _=(i,l,c)=>l in i?B(i,l,{enumerable:!0,configurable:!0,writable:!0,value:c}):i[l]=c;var o=(i,l,c)=>_(i,typeof l!="symbol"?l+"":l,c);function l(n){return n.split(",").map(e=>e.trim().toLowerCase()).filter(e=>e)}function c(n){return(n==null?void 0:n.replace(/\n|\t/," ").replace(/ {2,}/," ").trim().toLowerCase())||""}function w(n){return((n==null?void 0:n.match(/\d+/gm))||[0]).reverse().map((e,t)=>parseInt(e)*60**t).reduce((e,t)=>e+t)}function T(n,e){return Number.isInteger(parseInt(n))?parseInt(n):e}function E(n){const e=n.split(";").flatMap(t=>{const r=t.match(/([\+\w+]+):(\w+)?/),s=r==null?void 0:r[2];if(s)return r[1].split("+").map(a=>({[a]:s}))}).filter(t=>t);return Object.assign({},...e)}function k(n){return n.replace(/url\("|\"\).*/g,"")}class u{constructor(e){o(this,"observer");this.callback=e,this.observer=new IntersectionObserver(this.handleIntersection.bind(this))}observe(e){this.observer.observe(e)}throttle(e,t){this.observer.unobserve(e),setTimeout(()=>this.observer.observe(e),t)}handleIntersection(e){for(const t of e)t.isIntersecting&&this.callback(t.target)}static observeWhile(e,t,r){const s=new u(async a=>{await t()&&s.throttle(a,r)});return s.observe(e),s}}class h{constructor(e,t="data-lazy-load",r=!0){o(this,"lazyImgObserver");o(this,"delazify",e=>{this.lazyImgObserver.observer.unobserve(e),e.src=e.getAttribute(this.attributeName),this.removeTagAfter&&e.removeAttribute(this.attributeName)});this.attributeName=t,this.removeTagAfter=r,this.lazyImgObserver=new u(s=>{e(s,this.delazify)})}lazify(e,t,r){!t||!r||(t.setAttribute(this.attributeName,r),t.src="",this.lazyImgObserver.observe(t))}static create(e){return new h((r,s)=>{e(r)&&s(r)})}}function A(n,e=6,t=1){return(n+t)%e||e}function f(n){const e=new DOMParser().parseFromString(n,"text/html").body;return e.children.length>1?e:e.firstElementChild}function m(n,e){for(const t of e.attributes)t.nodeValue&&n.setAttribute(t.nodeName,t.nodeValue)}function I(n,e){var r;const t=document.createElement(e);return m(t,n),t.innerHTML=n.innerHTML,(r=n.parentNode)==null||r.replaceChild(t,n),t}function S(n){return Array.from(n).reduce((e,t)=>(t.parentElement&&e.includes(t.parentElement)&&e.push(t.parentElement),e),[])}function v(n){return n.nextElementSibling?n.nextElementSibling:n.parentElement?v(n.parentElement):null}function g(n,e,t){const r=new MutationObserver(s=>{const a=n.querySelector(e);a&&(r.disconnect(),t(a))});r.observe(document.body,{childList:!0,subtree:!0})}function O(n,e){let t=n.children.length;new MutationObserver((s,a)=>{for(const b of s)b.type==="childList"&&t!==n.children.length&&(t=n.children.length,e(a,t))}).observe(n,{childList:!0})}function L(n,e,t=1e3,r={childList:!0,subtree:!0,attributes:!0}){let s,a;new MutationObserver((G,K)=>{const y=Date.now();s&&y-s<t&&a&&clearTimeout(a),a=setTimeout(e,t),s=y}).observe(n,r)}function M(n={append:"",after:"",button:"",cbBefore:()=>{}}){var t,r;const e=f(n.button);n.append&&((t=document.querySelector(n.append))==null||t.append(e)),n.after&&((r=document.querySelector(n.after))==null||r.after(e)),e.addEventListener("click",s=>{s.preventDefault(),n.cbBefore&&n.cbBefore(),g(document.body,"video",a=>{window.location.href=a.getAttribute("src")})})}const p=["Mozilla/5.0 (Linux; Android 10; K)","AppleWebKit/537.36 (KHTML, like Gecko)","Chrome/114.0.0.0 Mobile Safari/537.36"].join(" ");function d(n,e={html:!1,mobile:!1}){const t={};return e.mobile&&Object.assign(t,{headers:new Headers({"User-Agent":p})}),fetch(n,t).then(r=>r.text()).then(r=>e.html?f(r):r)}const P=n=>d(n,{html:!0}),z=n=>d(n);function F(n){const e=new FormData;return Object.entries(n).forEach(([t,r])=>e.append(t,r)),e}function C(n,e,t){for(const r of e)n.addEventListener(r,t,!0)}class D{constructor(e,t=!0){o(this,"tick");o(this,"callbackFinal");this.delay=e,this.startImmediate=t,this.tick=null,this.delay=e,this.startImmediate=t}start(e,t=null){this.stop(),this.callbackFinal=t,this.startImmediate&&e(),this.tick=setInterval(e,this.delay)}stop(){this.tick!==null&&(clearInterval(this.tick),this.tick=null),this.callbackFinal&&(this.callbackFinal(),this.callbackFinal=null)}}function H(){return/iPhone|Android/i.test(navigator.userAgent)}async function N(n){const e=[];for await(const t of n)e.push(await t());return e}function j(n){return new Promise(e=>setTimeout(e,n))}class W{constructor(){o(this,"pull",[]);o(this,"lock",!1)}getHighPriorityFirst(e=0){if(e>3||this.pull.length===0)return;const t=this.pull.findIndex(r=>r.p===e);if(t>=0){const r=this.pull[t].v;return this.pull=this.pull.slice(0,t).concat(this.pull.slice(t+1)),r}return this.getHighPriorityFirst(e+1)}*pullGenerator(){for(;this.pull.length>0;)yield this.getHighPriorityFirst()}async processPull(){if(!this.lock){this.lock=!0;for await(const e of this.pullGenerator())await e();this.lock=!1}}push(e){this.pull.push(e),this.processPull()}}function U(n,e){const t=[];for(let r=0;r<n.length;r+=e)t.push(n.slice(r,r+e));return t}function q(n,e=1){return[...Array(n).keys()].map(t=>t+e)}i.LazyImgLoader=h,i.MOBILE_UA=p,i.Observer=u,i.SyncPull=W,i.Tick=D,i.chunks=U,i.circularShift=A,i.computeAsyncOneAtTime=N,i.copyAttributes=m,i.downloader=M,i.fetchHtml=P,i.fetchText=z,i.fetchWith=d,i.findNextSibling=v,i.getAllUniqueParents=S,i.isMob=H,i.listenEvents=C,i.objectToFormData=F,i.parseCSSUrl=k,i.parseDataParams=E,i.parseDom=f,i.parseIntegerOr=T,i.range=q,i.replaceElementTag=I,i.sanitizeStr=c,i.stringToWords=l,i.timeToSeconds=w,i.wait=j,i.waitForElementExists=g,i.watchDomChangesWithThrottle=L,i.watchElementChildrenCount=O,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})});
package/index.html ADDED
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Document</title>
8
+ <style>
9
+ body {
10
+ background: radial-gradient(red, green, blue);
11
+ height: 100vh;
12
+ overflow: hidden;
13
+ margin: 0;
14
+ }
15
+ </style>
16
+ <script src="./dist/billy-herrington-utils.umd.js"></script>
17
+ </head>
18
+
19
+ <body>
20
+ <script>
21
+ </script>
22
+ </body>
23
+
24
+ </html>
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "billy-herrington-utils",
3
+ "description": "daddy told us not to be ashamed of our utils",
4
+ "version": "1.0.0",
5
+ "license": "MIT",
6
+ "keywords": ["utils", "observer", "dom", "fetch", "arrays"],
7
+ "author": "smartacephale atm.mormon@protonmail.com (https://github.com/smartacephale)",
8
+ "type": "module",
9
+ "homepage": "https://github.com/smartacephale/billy-herrington-utils#readme",
10
+ "main": "./dist/billy-herrington-utils.umd.js",
11
+ "module": "./dist/billy-herrington-utils.es.js",
12
+ "repository": "github:smartacephale/billy-herrington-utils",
13
+ "bugs": {
14
+ "url": "https://github.com/smartacephale/billy-herrington-utils/issues",
15
+ "email": "atm.mormon@protonmail.com"
16
+ },
17
+ "browser": "./dist/billy-herrington-utils.es.js",
18
+ "unpkg": "./dist/billy-herrington-utils.es.js",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/billy-herrington-utils.es.js",
22
+ "require": "./dist/billy-herrington-utils.umd.cjs"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "dev": "vite",
27
+ "build": "tsc && vite build",
28
+ "preview": "vite preview"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.5.3",
32
+ "vite": "^5.4.0"
33
+ }
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ export { stringToWords, sanitizeStr } from "./utils/strings";
2
+ export { timeToSeconds, parseCSSUrl, parseDataParams, parseIntegerOr } from "./utils/parsers";
3
+ export { Observer, LazyImgLoader } from "./utils/observers";
4
+ export { circularShift } from "./utils/math";
5
+ export { MOBILE_UA, fetchHtml, fetchText, fetchWith, objectToFormData } from "./utils/fetch";
6
+ export { Tick, listenEvents } from "./utils/events";
7
+ export {
8
+ parseDom,
9
+ copyAttributes,
10
+ downloader,
11
+ findNextSibling,
12
+ getAllUniqueParents,
13
+ replaceElementTag,
14
+ waitForElementExists,
15
+ watchDomChangesWithThrottle,
16
+ watchElementChildrenCount
17
+ } from "./utils/dom";
18
+ export { isMob } from "./utils/device";
19
+ export { computeAsyncOneAtTime, SyncPull, wait } from "./utils/async";
20
+ export { chunks, range } from "./utils/arrays";
@@ -0,0 +1,11 @@
1
+ export function chunks<T>(arr: Array<T>, n: number): Array<Array<T>> {
2
+ const res = [];
3
+ for (let i = 0; i < arr.length; i += n) {
4
+ res.push(arr.slice(i, i + n));
5
+ }
6
+ return res;
7
+ }
8
+
9
+ export function range(size: number, startAt = 1): Array<number> {
10
+ return [...Array(size).keys()].map(i => i + startAt);
11
+ }
@@ -0,0 +1,54 @@
1
+ // https://2ality.com/2016/10/asynchronous-iteration.html
2
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
3
+ export async function computeAsyncOneAtTime(iterable: Iterable<any>) {
4
+ const res = [];
5
+ for await (const f of iterable) {
6
+ res.push(await f());
7
+ }
8
+ return res;
9
+ }
10
+
11
+ export function wait(milliseconds: number) {
12
+ return new Promise(resolve => setTimeout(resolve, milliseconds));
13
+ }
14
+
15
+ // do async one at time
16
+ export class SyncPull {
17
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
18
+ pull: Array<any> = [];
19
+ lock = false;
20
+
21
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
22
+ getHighPriorityFirst(p = 0): any {
23
+ if (p > 3 || this.pull.length === 0) return undefined;
24
+ const i = this.pull.findIndex(e => e.p === p);
25
+ if (i >= 0) {
26
+ const res = this.pull[i].v;
27
+ this.pull = this.pull.slice(0, i).concat(this.pull.slice(i + 1));
28
+ return res;
29
+ }
30
+ return this.getHighPriorityFirst(p + 1);
31
+ }
32
+
33
+ *pullGenerator() {
34
+ while (this.pull.length > 0) {
35
+ yield this.getHighPriorityFirst();
36
+ }
37
+ }
38
+
39
+ async processPull() {
40
+ if (!this.lock) {
41
+ this.lock = true;
42
+ for await (const f of this.pullGenerator()) {
43
+ await f();
44
+ }
45
+ this.lock = false;
46
+ }
47
+ }
48
+
49
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
50
+ push(x: any) {
51
+ this.pull.push(x);
52
+ this.processPull();
53
+ }
54
+ }
@@ -0,0 +1,3 @@
1
+ export function isMob() {
2
+ return /iPhone|Android/i.test(navigator.userAgent);
3
+ }
@@ -0,0 +1,92 @@
1
+ export function parseDom(html: string): HTMLElement {
2
+ const parsed = new DOMParser().parseFromString(html, 'text/html').body;
3
+ return parsed.children.length > 1 ? parsed : parsed.firstElementChild as HTMLElement;
4
+ }
5
+
6
+ export function copyAttributes(target: HTMLElement | Element, source: HTMLElement | Element) {
7
+ for (const attr of source.attributes) {
8
+ attr.nodeValue && target.setAttribute(attr.nodeName, attr.nodeValue);
9
+ }
10
+ }
11
+
12
+ export function replaceElementTag(e: HTMLElement | Element, tagName: string) {
13
+ const newTagElement = document.createElement(tagName);
14
+ copyAttributes(newTagElement, e);
15
+ newTagElement.innerHTML = e.innerHTML;
16
+ e.parentNode?.replaceChild(newTagElement, e);
17
+ return newTagElement;
18
+ }
19
+
20
+ export function getAllUniqueParents(elements: HTMLCollection): Array<HTMLElement | Element> {
21
+ return Array.from(elements).reduce((acc, v) => {
22
+ if (v.parentElement && acc.includes(v.parentElement as HTMLElement)) { acc.push(v.parentElement); }
23
+ return acc;
24
+ }, [] as Array<HTMLElement | Element>);
25
+ }
26
+
27
+ export function findNextSibling(el: HTMLElement | Element) {
28
+ if (el.nextElementSibling) return el.nextElementSibling;
29
+ if (el.parentElement) return findNextSibling(el.parentElement);
30
+ return null;
31
+ }
32
+
33
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
34
+ export function waitForElementExists(parent: HTMLElement | Element, selector: string, callback: any): void {
35
+ const observer = new MutationObserver((_mutations) => {
36
+ const el = parent.querySelector(selector);
37
+ if (el) {
38
+ observer.disconnect();
39
+ callback(el);
40
+ }
41
+ });
42
+ observer.observe(document.body, { childList: true, subtree: true });
43
+ }
44
+
45
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
46
+ export function watchElementChildrenCount(element: HTMLElement | Element, callback: any): void {
47
+ let count = element.children.length;
48
+ const observer = new MutationObserver((mutationList, observer) => {
49
+ for (const mutation of mutationList) {
50
+ if (mutation.type === "childList") {
51
+ if (count !== element.children.length) {
52
+ count = element.children.length;
53
+ callback(observer, count);
54
+ }
55
+ }
56
+ }
57
+ });
58
+ observer.observe(element, { childList: true });
59
+ }
60
+
61
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
62
+ export function watchDomChangesWithThrottle(element: HTMLElement | Element, callback: any,
63
+ throttle = 1000, options: Record<string, boolean> = { childList: true, subtree: true, attributes: true }) {
64
+ let lastMutationTime: number;
65
+ let timeout: number;
66
+ const observer = new MutationObserver((_mutationList, _observer) => {
67
+ const now = Date.now();
68
+ if (lastMutationTime && now - lastMutationTime < throttle) {
69
+ timeout && clearTimeout(timeout);
70
+ }
71
+ timeout = setTimeout(callback, throttle);
72
+ lastMutationTime = now;
73
+ });
74
+ observer.observe(element, options);
75
+ }
76
+
77
+ export function downloader(options = { append: "", after: "", button: "", cbBefore: () => { } }) {
78
+ const btn = parseDom(options.button);
79
+
80
+ if (options.append) document.querySelector(options.append)?.append(btn);
81
+ if (options.after) document.querySelector(options.after)?.after(btn);
82
+
83
+ btn.addEventListener('click', (e) => {
84
+ e.preventDefault();
85
+
86
+ if (options.cbBefore) options.cbBefore();
87
+
88
+ waitForElementExists(document.body, 'video', (video: HTMLVideoElement) => {
89
+ window.location.href = video.getAttribute('src') as string;
90
+ });
91
+ });
92
+ }
@@ -0,0 +1,37 @@
1
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
2
+ export function listenEvents(dom: HTMLElement | Element, events: Array<string>, callback: any): void {
3
+ for (const e of events) {
4
+ dom.addEventListener(e, callback, true);
5
+ }
6
+ }
7
+
8
+ export class Tick {
9
+ private tick: null | number;
10
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
11
+ private callbackFinal: any;
12
+
13
+ constructor(private delay: number, private startImmediate = true) {
14
+ this.tick = null;
15
+ this.delay = delay;
16
+ this.startImmediate = startImmediate;
17
+ }
18
+
19
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
20
+ start(callback: any, callbackFinal = null) {
21
+ this.stop();
22
+ this.callbackFinal = callbackFinal;
23
+ if (this.startImmediate) callback();
24
+ this.tick = setInterval(callback, this.delay);
25
+ }
26
+
27
+ stop() {
28
+ if (this.tick !== null) {
29
+ clearInterval(this.tick);
30
+ this.tick = null;
31
+ }
32
+ if (this.callbackFinal) {
33
+ this.callbackFinal();
34
+ this.callbackFinal = null;
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,22 @@
1
+ import { parseDom } from "../dom";
2
+
3
+ export const MOBILE_UA = [
4
+ 'Mozilla/5.0 (Linux; Android 10; K)',
5
+ 'AppleWebKit/537.36 (KHTML, like Gecko)',
6
+ 'Chrome/114.0.0.0 Mobile Safari/537.36'].join(' ');
7
+
8
+ export function fetchWith(url: string, options: Record<string, boolean> = { html: false, mobile: false }) {
9
+ const reqOpts = {};
10
+ if (options.mobile) Object.assign(reqOpts, { headers: new Headers({ "User-Agent": MOBILE_UA }) });
11
+ return fetch(url, reqOpts).then((r) => r.text()).then(r => options.html ? parseDom(r) : r);
12
+ }
13
+
14
+ export const fetchHtml = (url: string) => fetchWith(url, { html: true });
15
+
16
+ export const fetchText = (url: string) => fetchWith(url);
17
+
18
+ export function objectToFormData(object: Record<string, number | boolean | string>): FormData {
19
+ const formData = new FormData();
20
+ Object.entries(object).forEach(([k, v]) => formData.append(k, v as string));
21
+ return formData;
22
+ }
@@ -0,0 +1,3 @@
1
+ export function circularShift(n: number, c = 6, s = 1): number {
2
+ return (n + s) % c || c;
3
+ }
@@ -0,0 +1,67 @@
1
+ export class Observer {
2
+ public observer: IntersectionObserver;
3
+ constructor(private callback: (entry: Element) => void) {
4
+ this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
5
+ }
6
+
7
+ observe(target: Element) {
8
+ this.observer.observe(target);
9
+ }
10
+
11
+ throttle(target: Element, throttleTime: number) {
12
+ this.observer.unobserve(target);
13
+ setTimeout(() => this.observer.observe(target), throttleTime);
14
+ }
15
+
16
+ handleIntersection(entries: Iterable<IntersectionObserverEntry>) {
17
+ for (const entry of entries) {
18
+ if (entry.isIntersecting) {
19
+ this.callback(entry.target);
20
+ }
21
+ }
22
+ }
23
+
24
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
25
+ static observeWhile(target: Element, callback: any, throttleTime: number) {
26
+ const observer_ = new Observer(async (target: Element) => {
27
+ const condition = await callback();
28
+ if (condition) observer_.throttle(target, throttleTime);
29
+ });
30
+ observer_.observe(target);
31
+ return observer_;
32
+ }
33
+ }
34
+
35
+ export class LazyImgLoader {
36
+ public lazyImgObserver: Observer;
37
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
38
+ constructor(callback: any, private attributeName = 'data-lazy-load', private removeTagAfter = true) {
39
+ this.lazyImgObserver = new Observer((target: Element) => {
40
+ callback(target, this.delazify);
41
+ });
42
+ }
43
+
44
+ lazify(_target: Element, img: HTMLImageElement, imgSrc: string) {
45
+ if (!img || !imgSrc) return;
46
+ img.setAttribute(this.attributeName, imgSrc);
47
+ img.src = '';
48
+ this.lazyImgObserver.observe(img);
49
+ }
50
+
51
+ delazify = (target: HTMLImageElement) => {
52
+ this.lazyImgObserver.observer.unobserve(target);
53
+ target.src = target.getAttribute(this.attributeName) as string;
54
+ if (this.removeTagAfter) target.removeAttribute(this.attributeName);
55
+ }
56
+
57
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
58
+ static create(callback: any) {
59
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
60
+ const lazyImgLoader = new LazyImgLoader((target: Element, delazify: any) => {
61
+ if (callback(target)) {
62
+ delazify(target);
63
+ }
64
+ });
65
+ return lazyImgLoader;
66
+ }
67
+ }
@@ -0,0 +1,24 @@
1
+ export function timeToSeconds(t: string): number {
2
+ return (t?.match(/\d+/gm) || [0])
3
+ .reverse()
4
+ .map((s, i) => parseInt(s as string) * 60 ** i)
5
+ .reduce((a, b) => a + b);
6
+ }
7
+
8
+ export function parseIntegerOr(n: string | number, or: number): number {
9
+ return Number.isInteger(parseInt(n as string)) ? parseInt(n as string) : or;
10
+ }
11
+
12
+ // "data:02;body+head:async;void:;zero:;"
13
+ export function parseDataParams(str: string) {
14
+ const params = str.split(';').flatMap(s => {
15
+ const parsed = s.match(/([\+\w+]+):(\w+)?/);
16
+ const value = parsed?.[2];
17
+ if (value) return parsed[1].split('+').map(p => ({ [p]: value }));
18
+ }).filter(_ => _);
19
+ return Object.assign({}, ...params);
20
+ }
21
+
22
+ export function parseCSSUrl(s: string) {
23
+ return s.replace(/url\("|\"\).*/g, '');
24
+ }
@@ -0,0 +1,7 @@
1
+ export function stringToWords(s: string): Array<string> {
2
+ return s.split(",").map(s => s.trim().toLowerCase()).filter(_ => _);
3
+ }
4
+
5
+ export function sanitizeStr(s: string) {
6
+ return s?.replace(/\n|\t/, ' ').replace(/ {2,}/, ' ').trim().toLowerCase() || "";
7
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "isolatedModules": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["src", "main.js"]
23
+ }
package/vite.config.js ADDED
@@ -0,0 +1,27 @@
1
+ import path from "node:path";
2
+ import { defineConfig, loadEnv } from "vite";
3
+
4
+ export default ({ mode }) => {
5
+ return defineConfig({
6
+ define: {
7
+ "process.env": {},
8
+ },
9
+ run: {
10
+ entry: path.resolve(__dirname, "./src/index.html")
11
+ },
12
+ resolve: {
13
+ alias: {
14
+ vue: 'vue/dist/vue.esm-bundler.js',
15
+ }
16
+ },
17
+ build: {
18
+ watch: false,
19
+ lib: {
20
+ minify: true,
21
+ entry: path.resolve(__dirname, "./src/index.ts"),
22
+ name: "bhutils",
23
+ fileName: (format) => `billy-herrington-utils.${format}.js`,
24
+ }
25
+ },
26
+ });
27
+ };