billy-herrington-utils 2.0.7 → 2.1.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 +360 -62
- package/dist/billy-herrington-utils.es.js +275 -6251
- package/dist/billy-herrington-utils.es.js.map +1 -1
- package/dist/billy-herrington-utils.umd.js +321 -6253
- package/dist/billy-herrington-utils.umd.js.map +1 -1
- package/dist/index.d.ts +69 -190
- package/package.json +18 -7
- package/src/index.ts +1 -44
- package/src/types/index.ts +7 -0
- package/src/utils/arrays/index.ts +11 -5
- package/src/utils/async/index.ts +0 -88
- package/src/utils/dom/{observers.ts → dom-observers.ts} +3 -3
- package/src/utils/dom/index.ts +97 -16
- package/src/utils/events/index.ts +2 -37
- package/src/utils/events/on-hover.ts +42 -0
- package/src/utils/events/tick.ts +27 -0
- package/src/utils/fetch/index.ts +30 -28
- package/src/utils/index.ts +39 -0
- package/src/utils/objects/index.ts +19 -18
- package/src/utils/objects/memoize.ts +25 -0
- package/src/utils/observers/index.ts +4 -28
- package/src/utils/observers/lazy-image-loader.ts +27 -0
- package/src/utils/parsers/index.ts +2 -20
- package/src/utils/parsers/time-parser.ts +28 -0
- package/src/utils/strings/regexes.ts +12 -8
- package/src/types/globals.d.ts +0 -0
- package/src/userscripts/data-manager/data-filter.ts +0 -110
- package/src/userscripts/data-manager/index.ts +0 -141
- package/src/userscripts/infinite-scroll/index.ts +0 -106
- package/src/userscripts/pagination-parsing/index.ts +0 -55
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategy.ts +0 -42
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyDataParams.ts +0 -66
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyPathnameParams.ts +0 -77
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategySearchParams.ts +0 -56
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyTrash.ts +0 -33
- package/src/userscripts/pagination-parsing/pagination-strategies/index.ts +0 -5
- package/src/userscripts/pagination-parsing/pagination-utils/index.ts +0 -69
- package/src/userscripts/router/router.ts +0 -71
- package/src/userscripts/rules/index.ts +0 -241
- package/src/userscripts/types/index.ts +0 -19
- package/src/utils/device/index.ts +0 -3
- package/src/utils/userscript/index.ts +0 -10
package/src/utils/dom/index.ts
CHANGED
|
@@ -1,41 +1,74 @@
|
|
|
1
1
|
import { sanitizeStr } from '../strings';
|
|
2
|
-
import { waitForElementToAppear } from './observers';
|
|
2
|
+
import { waitForElementToAppear } from './dom-observers';
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
5
|
waitForElementToAppear,
|
|
6
6
|
waitForElementToDisappear,
|
|
7
7
|
watchDomChangesWithThrottle,
|
|
8
8
|
watchElementChildrenCount,
|
|
9
|
-
} from './observers';
|
|
9
|
+
} from './dom-observers';
|
|
10
10
|
|
|
11
|
-
export function
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export function querySelectorLast<T extends Element = HTMLElement>(
|
|
12
|
+
root: ParentNode = document,
|
|
13
|
+
selector: string,
|
|
14
|
+
): T | undefined {
|
|
15
|
+
const nodes = root.querySelectorAll<T>(selector);
|
|
16
|
+
return nodes.length > 0 ? nodes[nodes.length - 1] : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function querySelectorLastNumber(selector: string, e: ParentNode = document) {
|
|
20
|
+
const text = querySelectorText(e, selector);
|
|
21
|
+
return Number(text.match(/\d+/g)?.pop() || 0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function querySelectorText(e: ParentNode, selector?: string): string {
|
|
25
|
+
if (typeof selector !== 'string') return '';
|
|
26
|
+
const text = e.querySelector<HTMLElement>(selector)?.innerText || '';
|
|
14
27
|
return sanitizeStr(text);
|
|
15
28
|
}
|
|
16
29
|
|
|
17
|
-
export function
|
|
30
|
+
export function parseHtml(html: string): HTMLElement {
|
|
18
31
|
const parsed = new DOMParser().parseFromString(html, 'text/html').body;
|
|
19
|
-
|
|
32
|
+
if (parsed.children.length > 1) return parsed;
|
|
33
|
+
return parsed.firstElementChild as HTMLElement;
|
|
20
34
|
}
|
|
21
35
|
|
|
22
|
-
export function copyAttributes(target:
|
|
36
|
+
export function copyAttributes<T extends Element = HTMLElement>(target: T, source: T) {
|
|
23
37
|
for (const attr of source.attributes) {
|
|
24
|
-
|
|
38
|
+
if (attr.nodeValue) {
|
|
39
|
+
target.setAttribute(attr.nodeName, attr.nodeValue);
|
|
40
|
+
}
|
|
25
41
|
}
|
|
26
42
|
}
|
|
27
43
|
|
|
28
44
|
export function replaceElementTag(e: HTMLElement, tagName: string) {
|
|
29
45
|
const newTagElement = document.createElement(tagName);
|
|
30
46
|
copyAttributes(newTagElement, e);
|
|
47
|
+
|
|
31
48
|
newTagElement.innerHTML = e.innerHTML;
|
|
32
49
|
e.parentNode?.replaceChild(newTagElement, e);
|
|
50
|
+
|
|
33
51
|
return newTagElement;
|
|
34
52
|
}
|
|
35
53
|
|
|
36
|
-
export function
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
export function removeClassesAndDataAttributes(
|
|
55
|
+
element: HTMLElement,
|
|
56
|
+
keyword: string,
|
|
57
|
+
): void {
|
|
58
|
+
Array.from(element.classList).forEach((className) => {
|
|
59
|
+
if (className.includes(keyword)) {
|
|
60
|
+
element.classList.remove(className);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
65
|
+
if (attr.name.startsWith('data-') && attr.name.includes(keyword)) {
|
|
66
|
+
element.removeAttribute(attr.name);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getCommonParents(elements: HTMLCollection | HTMLElement[]): HTMLElement[] {
|
|
39
72
|
const parents = Array.from(elements)
|
|
40
73
|
.map((el) => el.parentElement)
|
|
41
74
|
.filter((parent): parent is HTMLElement => parent !== null);
|
|
@@ -43,12 +76,60 @@ export function getAllUniqueParents(
|
|
|
43
76
|
return [...new Set(parents)];
|
|
44
77
|
}
|
|
45
78
|
|
|
46
|
-
export function findNextSibling(
|
|
47
|
-
if (
|
|
48
|
-
if (
|
|
79
|
+
export function findNextSibling<T extends Element = HTMLElement>(e: T) {
|
|
80
|
+
if (e.nextElementSibling) return e.nextElementSibling;
|
|
81
|
+
if (e.parentElement) return findNextSibling(e.parentElement);
|
|
49
82
|
return null;
|
|
50
83
|
}
|
|
51
84
|
|
|
85
|
+
export function checkHomogenity<T extends HTMLElement>(
|
|
86
|
+
a: T,
|
|
87
|
+
b: T,
|
|
88
|
+
options: { id?: boolean; className?: boolean },
|
|
89
|
+
) {
|
|
90
|
+
if (!a || !b) return false;
|
|
91
|
+
|
|
92
|
+
if (options.id) {
|
|
93
|
+
if (a.id !== b.id) return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (options.className) {
|
|
97
|
+
const ca = a.className;
|
|
98
|
+
const cb = b.className;
|
|
99
|
+
if (!(ca.length > cb.length ? ca.includes(cb) : cb.includes(ca))) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function instantiateTemplate(
|
|
108
|
+
sourceSelector: string,
|
|
109
|
+
attributeUpdates: Record<string, string>,
|
|
110
|
+
contentUpdates: Record<string, string>,
|
|
111
|
+
): string {
|
|
112
|
+
const source = document.querySelector(sourceSelector) as HTMLElement;
|
|
113
|
+
|
|
114
|
+
const wrapper = document.createElement('div');
|
|
115
|
+
const clone = source.cloneNode(true);
|
|
116
|
+
wrapper.append(clone);
|
|
117
|
+
|
|
118
|
+
Object.entries(attributeUpdates).forEach(([attrName, attrValue]) => {
|
|
119
|
+
wrapper.querySelectorAll(`[${attrName}]`).forEach((element) => {
|
|
120
|
+
element.setAttribute(attrName, attrValue);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
Object.entries(contentUpdates).forEach(([childSelector, textValue]) => {
|
|
125
|
+
wrapper.querySelectorAll<HTMLElement>(childSelector).forEach((element) => {
|
|
126
|
+
element.innerText = textValue;
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return wrapper.innerHTML;
|
|
131
|
+
}
|
|
132
|
+
|
|
52
133
|
export function exterminateVideo(video: HTMLVideoElement) {
|
|
53
134
|
video.removeAttribute('src');
|
|
54
135
|
video.load();
|
|
@@ -58,7 +139,7 @@ export function exterminateVideo(video: HTMLVideoElement) {
|
|
|
58
139
|
export function downloader(
|
|
59
140
|
options = { append: '', after: '', button: '', cbBefore: () => {} },
|
|
60
141
|
) {
|
|
61
|
-
const btn =
|
|
142
|
+
const btn = parseHtml(options.button);
|
|
62
143
|
|
|
63
144
|
if (options.append) document.querySelector(options.append)?.append(btn);
|
|
64
145
|
if (options.after) document.querySelector(options.after)?.after(btn);
|
|
@@ -1,37 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
events: Array<string>,
|
|
4
|
-
callback: (e: Event) => void,
|
|
5
|
-
): void {
|
|
6
|
-
for (const e of events) {
|
|
7
|
-
dom.addEventListener(e, callback, true);
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class Tick {
|
|
12
|
-
private tick?: number;
|
|
13
|
-
private callbackFinal?: () => void;
|
|
14
|
-
|
|
15
|
-
constructor(
|
|
16
|
-
private delay: number,
|
|
17
|
-
private startImmediate: boolean = true,
|
|
18
|
-
) {}
|
|
19
|
-
|
|
20
|
-
public start(callback: () => void, callbackFinal?: () => void): void {
|
|
21
|
-
this.stop();
|
|
22
|
-
this.callbackFinal = callbackFinal;
|
|
23
|
-
if (this.startImmediate) callback();
|
|
24
|
-
this.tick = window.setInterval(callback, this.delay);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public stop(): void {
|
|
28
|
-
if (this.tick !== undefined) {
|
|
29
|
-
clearInterval(this.tick);
|
|
30
|
-
this.tick = undefined;
|
|
31
|
-
}
|
|
32
|
-
if (this.callbackFinal) {
|
|
33
|
-
this.callbackFinal();
|
|
34
|
-
this.callbackFinal = undefined;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
1
|
+
export { OnHover } from './on-hover';
|
|
2
|
+
export { Tick } from './tick';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class OnHover {
|
|
2
|
+
private handleLeaveEvent() {
|
|
3
|
+
this.onLeave?.(this.target as HTMLElement);
|
|
4
|
+
this.onOverFinally?.();
|
|
5
|
+
this.target = undefined;
|
|
6
|
+
this.onOverFinally = undefined;
|
|
7
|
+
this.leaveSubject = undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private handleEvent(e: PointerEvent) {
|
|
11
|
+
const currentTarget = e.target as HTMLElement;
|
|
12
|
+
if (!this.subjectSelector(currentTarget) || this.target === currentTarget) return;
|
|
13
|
+
this.leaveSubject?.dispatchEvent(new PointerEvent('pointerleave'));
|
|
14
|
+
|
|
15
|
+
this.target = currentTarget;
|
|
16
|
+
const result = this.onOver(this.target);
|
|
17
|
+
this.onOverFinally = result?.onOverCallback;
|
|
18
|
+
this.leaveSubject = result?.leaveTarget || this.target;
|
|
19
|
+
this.leaveSubject.addEventListener('pointerleave', (_) => this.handleLeaveEvent(), {
|
|
20
|
+
once: true,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private target: HTMLElement | undefined;
|
|
25
|
+
private leaveSubject: HTMLElement | undefined;
|
|
26
|
+
private onOverFinally: (() => void) | undefined;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private container: HTMLElement,
|
|
30
|
+
private subjectSelector: (target: HTMLElement) => boolean,
|
|
31
|
+
private onOver: (
|
|
32
|
+
target: HTMLElement,
|
|
33
|
+
) => void | { onOverCallback?: () => void; leaveTarget?: HTMLElement },
|
|
34
|
+
private onLeave?: (target: HTMLElement) => void,
|
|
35
|
+
) {
|
|
36
|
+
this.container.addEventListener('pointerover', (e) => this.handleEvent(e));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static create(...args: ConstructorParameters<typeof OnHover>) {
|
|
40
|
+
return new OnHover(...args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class Tick {
|
|
2
|
+
private tick?: number;
|
|
3
|
+
private callbackFinal?: () => void;
|
|
4
|
+
|
|
5
|
+
constructor(
|
|
6
|
+
private delay: number,
|
|
7
|
+
private startImmediate: boolean = true,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
public start(callback: () => void, callbackFinal?: () => void): void {
|
|
11
|
+
this.stop();
|
|
12
|
+
this.callbackFinal = callbackFinal;
|
|
13
|
+
if (this.startImmediate) callback();
|
|
14
|
+
this.tick = window.setInterval(callback, this.delay);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public stop(): void {
|
|
18
|
+
if (this.tick !== undefined) {
|
|
19
|
+
clearInterval(this.tick);
|
|
20
|
+
this.tick = undefined;
|
|
21
|
+
}
|
|
22
|
+
if (this.callbackFinal) {
|
|
23
|
+
this.callbackFinal();
|
|
24
|
+
this.callbackFinal = undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/utils/fetch/index.ts
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseHtml } from '../dom';
|
|
2
2
|
|
|
3
|
-
export const MOBILE_UA =
|
|
4
|
-
'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
export const MOBILE_UA = {
|
|
4
|
+
'User-Agent': [
|
|
5
|
+
'Mozilla/5.0 (Linux; Android 10; K)',
|
|
6
|
+
'AppleWebKit/537.36 (KHTML, like Gecko)',
|
|
7
|
+
'Chrome/114.0.0.0 Mobile Safari/537.36',
|
|
8
|
+
].join(' '),
|
|
9
|
+
} as const;
|
|
8
10
|
|
|
9
|
-
export async function fetchWith(
|
|
10
|
-
|
|
11
|
-
options
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export async function fetchWith<T extends JSON | string | HTMLElement>(
|
|
12
|
+
input: RequestInfo | URL,
|
|
13
|
+
options: {
|
|
14
|
+
init?: RequestInit;
|
|
15
|
+
type: 'json' | 'html' | 'text';
|
|
16
|
+
mobile?: boolean;
|
|
17
|
+
},
|
|
18
|
+
): Promise<T> {
|
|
19
|
+
const requestInit: RequestInit = options.init || {};
|
|
14
20
|
|
|
15
|
-
if (options
|
|
16
|
-
Object.assign(
|
|
21
|
+
if (options.mobile) {
|
|
22
|
+
Object.assign(requestInit, { headers: new Headers(MOBILE_UA) });
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
.then((r) => r.text())
|
|
21
|
-
.then((r) => (options?.html ? parseDom(r) : r));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const fetchHtml = (url: string) =>
|
|
25
|
-
fetchWith(url, { html: true }) as Promise<HTMLElement>;
|
|
25
|
+
const r = await fetch(input, requestInit).then((r) => r);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const formData = new FormData();
|
|
31
|
-
Object.entries(obj).forEach(([k, v]) => {
|
|
32
|
-
formData.append(k, v as string);
|
|
33
|
-
});
|
|
34
|
-
return formData;
|
|
27
|
+
if (options.type === 'json') return (await r.json()) as T;
|
|
28
|
+
if (options.type === 'html') return parseHtml(await r.text()) as T;
|
|
29
|
+
return (await r.text()) as T;
|
|
35
30
|
}
|
|
31
|
+
|
|
32
|
+
export const fetchJson = (input: RequestInfo | URL) =>
|
|
33
|
+
fetchWith<JSON>(input, { type: 'json' });
|
|
34
|
+
export const fetchHtml = (input: RequestInfo | URL) =>
|
|
35
|
+
fetchWith<HTMLElement>(input, { type: 'html' });
|
|
36
|
+
export const fetchText = (input: RequestInfo | URL) =>
|
|
37
|
+
fetchWith<string>(input, { type: 'text' });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export { chunks, range } from './arrays';
|
|
2
|
+
export { wait } from './async';
|
|
3
|
+
export {
|
|
4
|
+
checkHomogenity,
|
|
5
|
+
copyAttributes,
|
|
6
|
+
downloader,
|
|
7
|
+
exterminateVideo,
|
|
8
|
+
findNextSibling,
|
|
9
|
+
getCommonParents,
|
|
10
|
+
instantiateTemplate,
|
|
11
|
+
parseHtml,
|
|
12
|
+
querySelectorLast,
|
|
13
|
+
querySelectorLastNumber,
|
|
14
|
+
querySelectorText,
|
|
15
|
+
removeClassesAndDataAttributes,
|
|
16
|
+
replaceElementTag,
|
|
17
|
+
waitForElementToAppear,
|
|
18
|
+
waitForElementToDisappear,
|
|
19
|
+
watchDomChangesWithThrottle,
|
|
20
|
+
watchElementChildrenCount,
|
|
21
|
+
} from './dom';
|
|
22
|
+
export { OnHover, Tick } from './events';
|
|
23
|
+
export {
|
|
24
|
+
fetchHtml,
|
|
25
|
+
fetchJson,
|
|
26
|
+
fetchText,
|
|
27
|
+
fetchWith,
|
|
28
|
+
} from './fetch';
|
|
29
|
+
export { circularShift } from './math';
|
|
30
|
+
export { memoize, objectToFormData, propsDifference } from './objects';
|
|
31
|
+
export { LazyImgLoader, Observer } from './observers';
|
|
32
|
+
export {
|
|
33
|
+
parseCssUrl,
|
|
34
|
+
parseDataParams,
|
|
35
|
+
parseIntegerOr,
|
|
36
|
+
timeToSeconds,
|
|
37
|
+
} from './parsers';
|
|
38
|
+
export { sanitizeStr, splitWith } from './strings';
|
|
39
|
+
export { RegexFilter } from './strings/regexes';
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
export { memoize } from './memoize';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
(
|
|
5
|
-
|
|
3
|
+
export function objectToFormData<T extends {}>(obj: T): FormData {
|
|
4
|
+
const formData = new FormData();
|
|
5
|
+
Object.entries(obj).forEach(([k, v]) => {
|
|
6
|
+
formData.append(k, v as string);
|
|
7
|
+
});
|
|
8
|
+
return formData;
|
|
6
9
|
}
|
|
7
10
|
|
|
8
|
-
export function
|
|
9
|
-
|
|
11
|
+
export function propsDifference<
|
|
12
|
+
T extends Record<string, unknown>,
|
|
13
|
+
U extends object,
|
|
14
|
+
>(obj1: T | U, obj2: T | U): { d1: string[]; d2: string[] } {
|
|
15
|
+
const a = new Set(Object.getOwnPropertyNames(obj1));
|
|
16
|
+
const b = new Set(Object.getOwnPropertyNames(obj2));
|
|
10
17
|
|
|
11
|
-
const
|
|
12
|
-
|
|
18
|
+
const d1 = a.difference(b).values().toArray();
|
|
19
|
+
const d2 = b.difference(a).values().toArray();
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const result = fn(...args);
|
|
19
|
-
cache.set(key, result);
|
|
20
|
-
|
|
21
|
-
return result;
|
|
22
|
-
}) as MemoizedFunction<T>;
|
|
21
|
+
return { d1, d2 };
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
export function parseIntegerOr(n: string, or: number | string) {
|
|
25
|
+
return ((num) => (Number.isNaN(num) ? or : num))(parseInt(n));
|
|
25
26
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AnyFunction } from '../../types';
|
|
2
|
+
|
|
3
|
+
export interface MemoizedFunction<T extends AnyFunction> extends CallableFunction {
|
|
4
|
+
(...args: Parameters<T>): ReturnType<T>;
|
|
5
|
+
clear: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function memoize<T extends AnyFunction>(fn: T): MemoizedFunction<T> {
|
|
9
|
+
const cache = new Map<string, ReturnType<T>>();
|
|
10
|
+
|
|
11
|
+
const memoizedFunction = ((...args: Parameters<T>): ReturnType<T> => {
|
|
12
|
+
const key = JSON.stringify(args);
|
|
13
|
+
|
|
14
|
+
if (cache.has(key)) {
|
|
15
|
+
return cache.get(key) as ReturnType<T>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = fn(...args);
|
|
19
|
+
cache.set(key, result);
|
|
20
|
+
|
|
21
|
+
return result;
|
|
22
|
+
}) as MemoizedFunction<T>;
|
|
23
|
+
|
|
24
|
+
return memoizedFunction;
|
|
25
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export { LazyImgLoader } from './lazy-image-loader';
|
|
2
|
+
|
|
1
3
|
export class Observer {
|
|
2
4
|
public observer: IntersectionObserver;
|
|
3
|
-
private timeout
|
|
5
|
+
private timeout?: number;
|
|
4
6
|
constructor(private callback: (entry: Element) => void) {
|
|
5
7
|
this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
|
|
6
8
|
}
|
|
@@ -11,7 +13,7 @@ export class Observer {
|
|
|
11
13
|
|
|
12
14
|
throttle(target: Element, throttleTime: number) {
|
|
13
15
|
this.observer.unobserve(target);
|
|
14
|
-
this.timeout = setTimeout(() => this.observer.observe(target), throttleTime);
|
|
16
|
+
this.timeout = window.setTimeout(() => this.observer.observe(target), throttleTime);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
handleIntersection(entries: Iterable<IntersectionObserverEntry>) {
|
|
@@ -40,29 +42,3 @@ export class Observer {
|
|
|
40
42
|
return observer_;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
|
|
44
|
-
export class LazyImgLoader {
|
|
45
|
-
public lazyImgObserver: Observer;
|
|
46
|
-
private attributeName = 'data-lazy-load';
|
|
47
|
-
|
|
48
|
-
constructor(shouldDelazify: (target: Element) => boolean) {
|
|
49
|
-
this.lazyImgObserver = new Observer((target: Element) => {
|
|
50
|
-
if (shouldDelazify(target)) {
|
|
51
|
-
this.delazify(target as HTMLImageElement);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
lazify(_target: Element, img?: HTMLImageElement, imgSrc?: string) {
|
|
57
|
-
if (!img || !imgSrc) return;
|
|
58
|
-
img.setAttribute(this.attributeName, imgSrc);
|
|
59
|
-
img.src = '';
|
|
60
|
-
this.lazyImgObserver.observe(img);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
delazify = (target: HTMLImageElement) => {
|
|
64
|
-
this.lazyImgObserver.observer.unobserve(target);
|
|
65
|
-
target.src = target.getAttribute(this.attributeName) as string;
|
|
66
|
-
target.removeAttribute(this.attributeName);
|
|
67
|
-
};
|
|
68
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Observer } from ".";
|
|
2
|
+
|
|
3
|
+
export class LazyImgLoader {
|
|
4
|
+
public lazyImgObserver: Observer;
|
|
5
|
+
private attributeName = 'data-lazy-load';
|
|
6
|
+
|
|
7
|
+
constructor(shouldDelazify: (target: Element) => boolean) {
|
|
8
|
+
this.lazyImgObserver = new Observer((target: Element) => {
|
|
9
|
+
if (shouldDelazify(target)) {
|
|
10
|
+
this.delazify(target as HTMLImageElement);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
lazify(_target: Element, img?: HTMLImageElement, imgSrc?: string) {
|
|
16
|
+
if (!img || !imgSrc) return;
|
|
17
|
+
img.setAttribute(this.attributeName, imgSrc);
|
|
18
|
+
img.src = '';
|
|
19
|
+
this.lazyImgObserver.observe(img);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
delazify = (target: HTMLImageElement) => {
|
|
23
|
+
this.lazyImgObserver.observer.unobserve(target);
|
|
24
|
+
target.src = target.getAttribute(this.attributeName) as string;
|
|
25
|
+
target.removeAttribute(this.attributeName);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
const regex: RegExp = /(?:(\d+)\s*h\s*)?(?:(\d+)\s*mi?n?\s*)?(?:(\d+)\s*sec)?/;
|
|
3
|
-
const match: RegExpMatchArray | null = timeString.match(regex);
|
|
4
|
-
const h: number = parseInt(match?.[1] || '0');
|
|
5
|
-
const m: number = parseInt(match?.[2] || '0');
|
|
6
|
-
const s: number = parseInt(match?.[3] || '0');
|
|
7
|
-
const pad = (num: number): string => String(num).padStart(2, '0');
|
|
8
|
-
return `${pad(h)}:${pad(m)}:${pad(s)}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// "01:22:03" -> 4923
|
|
12
|
-
export function timeToSeconds(t: string): number {
|
|
13
|
-
const r = /sec|min|h|m/.test(t) ? formatTimeToHHMMSS(t) : t;
|
|
14
|
-
return (r?.match(/\d+/gm) || [0])
|
|
15
|
-
.reverse()
|
|
16
|
-
|
|
17
|
-
.map((s, i) => parseInt(s as string) * 60 ** i)
|
|
18
|
-
.reduce((a, b) => a + b);
|
|
19
|
-
}
|
|
1
|
+
export { formatTimeToHHMMSS, timeToSeconds } from './time-parser';
|
|
20
2
|
|
|
21
3
|
export function parseIntegerOr(n: string | number, or: number): number {
|
|
22
4
|
const num = Number(n);
|
|
@@ -43,6 +25,6 @@ export function parseDataParams(str: string): Record<string, string> {
|
|
|
43
25
|
);
|
|
44
26
|
}
|
|
45
27
|
|
|
46
|
-
export function
|
|
28
|
+
export function parseCssUrl(s: string) {
|
|
47
29
|
return s.replace(/url\("|"\).*/g, '');
|
|
48
30
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a duration string (e.g., "1h 22min 3sec") to HH:MM:SS format.
|
|
3
|
+
* @param timeStr - The duration string to format.
|
|
4
|
+
* @returns A string in the format HH:MM:SS.
|
|
5
|
+
*/
|
|
6
|
+
export function formatTimeToHHMMSS(timeStr: string): string {
|
|
7
|
+
const pad = (num: number): string => num.toString().padStart(2, '0');
|
|
8
|
+
|
|
9
|
+
const h = timeStr.match(/(\d+)\s*h/)?.[1] || '0';
|
|
10
|
+
const m = timeStr.match(/(\d+)\s*mi?n/)?.[1] || '0';
|
|
11
|
+
const s = timeStr.match(/(\d+)\s*sec/)?.[1] || '0';
|
|
12
|
+
|
|
13
|
+
return `${pad(+h)}:${pad(+m)}:${pad(+s)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Converts a time string (HH:MM:SS or duration format) to total seconds.
|
|
18
|
+
* @param timeStr - The time string to convert.
|
|
19
|
+
* @returns The total number of seconds.
|
|
20
|
+
*/
|
|
21
|
+
export function timeToSeconds(timeStr: string): number {
|
|
22
|
+
const normalized = /[a-zA-Z]/.test(timeStr) ? formatTimeToHHMMSS(timeStr) : timeStr;
|
|
23
|
+
|
|
24
|
+
return normalized
|
|
25
|
+
.split(':')
|
|
26
|
+
.reverse()
|
|
27
|
+
.reduce((total, unit, index) => total + parseInt(unit, 10) * 60 ** index, 0);
|
|
28
|
+
}
|
|
@@ -4,21 +4,25 @@ import { splitWith } from '.';
|
|
|
4
4
|
export class RegexFilter {
|
|
5
5
|
private regexes: RegExp[];
|
|
6
6
|
|
|
7
|
-
constructor(str: string, flags: string = '
|
|
7
|
+
constructor(str: string, flags: string = 'gi') {
|
|
8
8
|
this.regexes = memoize(this.compileSearchRegex)(str, flags);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
// 'dog,bog,f:girl' or r:dog|bog... => [r/dog/i, r/bog/i, r/(^|\ )girl($|\ )/i]
|
|
12
12
|
private compileSearchRegex(str: string, flags: string): RegExp[] {
|
|
13
|
-
|
|
13
|
+
try {
|
|
14
|
+
if (str.startsWith('r:')) return [new RegExp(str.slice(2), flags)];
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const regexes = splitWith(str)
|
|
17
|
+
.map(
|
|
18
|
+
(s) => s.replace(/f:(\w+)/g, (_, w) => `(^|\\ |,)${w}($|\\ |,)`), // full word
|
|
19
|
+
)
|
|
20
|
+
.map((_) => new RegExp(_, flags));
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
return regexes;
|
|
23
|
+
} catch (_) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
public hasEvery(str: string) {
|
package/src/types/globals.d.ts
DELETED
|
File without changes
|