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.
Files changed (42) hide show
  1. package/README.md +360 -62
  2. package/dist/billy-herrington-utils.es.js +275 -6251
  3. package/dist/billy-herrington-utils.es.js.map +1 -1
  4. package/dist/billy-herrington-utils.umd.js +321 -6253
  5. package/dist/billy-herrington-utils.umd.js.map +1 -1
  6. package/dist/index.d.ts +69 -190
  7. package/package.json +18 -7
  8. package/src/index.ts +1 -44
  9. package/src/types/index.ts +7 -0
  10. package/src/utils/arrays/index.ts +11 -5
  11. package/src/utils/async/index.ts +0 -88
  12. package/src/utils/dom/{observers.ts → dom-observers.ts} +3 -3
  13. package/src/utils/dom/index.ts +97 -16
  14. package/src/utils/events/index.ts +2 -37
  15. package/src/utils/events/on-hover.ts +42 -0
  16. package/src/utils/events/tick.ts +27 -0
  17. package/src/utils/fetch/index.ts +30 -28
  18. package/src/utils/index.ts +39 -0
  19. package/src/utils/objects/index.ts +19 -18
  20. package/src/utils/objects/memoize.ts +25 -0
  21. package/src/utils/observers/index.ts +4 -28
  22. package/src/utils/observers/lazy-image-loader.ts +27 -0
  23. package/src/utils/parsers/index.ts +2 -20
  24. package/src/utils/parsers/time-parser.ts +28 -0
  25. package/src/utils/strings/regexes.ts +12 -8
  26. package/src/types/globals.d.ts +0 -0
  27. package/src/userscripts/data-manager/data-filter.ts +0 -110
  28. package/src/userscripts/data-manager/index.ts +0 -141
  29. package/src/userscripts/infinite-scroll/index.ts +0 -106
  30. package/src/userscripts/pagination-parsing/index.ts +0 -55
  31. package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategy.ts +0 -42
  32. package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyDataParams.ts +0 -66
  33. package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyPathnameParams.ts +0 -77
  34. package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategySearchParams.ts +0 -56
  35. package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyTrash.ts +0 -33
  36. package/src/userscripts/pagination-parsing/pagination-strategies/index.ts +0 -5
  37. package/src/userscripts/pagination-parsing/pagination-utils/index.ts +0 -69
  38. package/src/userscripts/router/router.ts +0 -71
  39. package/src/userscripts/rules/index.ts +0 -241
  40. package/src/userscripts/types/index.ts +0 -19
  41. package/src/utils/device/index.ts +0 -3
  42. package/src/utils/userscript/index.ts +0 -10
@@ -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 querySelectorText(el: HTMLElement, selector?: string): string {
12
- if (!selector || typeof selector !== 'string') return '';
13
- const text = el.querySelector<HTMLElement>(selector)?.innerText || '';
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 parseDom(html: string): HTMLElement {
30
+ export function parseHtml(html: string): HTMLElement {
18
31
  const parsed = new DOMParser().parseFromString(html, 'text/html').body;
19
- return parsed.children.length > 1 ? parsed : (parsed.firstElementChild as HTMLElement);
32
+ if (parsed.children.length > 1) return parsed;
33
+ return parsed.firstElementChild as HTMLElement;
20
34
  }
21
35
 
22
- export function copyAttributes(target: HTMLElement, source: HTMLElement) {
36
+ export function copyAttributes<T extends Element = HTMLElement>(target: T, source: T) {
23
37
  for (const attr of source.attributes) {
24
- attr.nodeValue && target.setAttribute(attr.nodeName, attr.nodeValue);
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 getAllUniqueParents(
37
- elements: HTMLCollection | HTMLElement[],
38
- ): HTMLElement[] {
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(el: HTMLElement) {
47
- if (el.nextElementSibling) return el.nextElementSibling;
48
- if (el.parentElement) return findNextSibling(el.parentElement);
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 = parseDom(options.button);
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 function listenEvents(
2
- dom: HTMLElement,
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
+ }
@@ -1,35 +1,37 @@
1
- import { parseDom } from '../dom';
1
+ import { parseHtml } from '../dom';
2
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',
7
- ].join(' ');
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
- url: string,
11
- options?: { html?: boolean; mobile?: boolean },
12
- ) {
13
- const reqOpts = {};
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?.mobile) {
16
- Object.assign(reqOpts, { headers: new Headers({ 'User-Agent': MOBILE_UA }) });
21
+ if (options.mobile) {
22
+ Object.assign(requestInit, { headers: new Headers(MOBILE_UA) });
17
23
  }
18
24
 
19
- return fetch(url, reqOpts)
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
- export const fetchText = (url: string) => fetchWith(url) as Promise<string>;
28
-
29
- export function objectToFormData<T extends {}>(obj: T): FormData {
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
- type AnyFunction = (...args: any[]) => any;
1
+ export { memoize } from './memoize';
2
2
 
3
- export interface MemoizedFunction<T extends AnyFunction> extends CallableFunction {
4
- (...args: Parameters<T>): ReturnType<T>;
5
- clear: () => void;
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 memoize<T extends AnyFunction>(fn: T): MemoizedFunction<T> {
9
- const cache = new Map<string, ReturnType<T>>();
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 memoizedFunction = ((...args: Parameters<T>): ReturnType<T> => {
12
- const key = JSON.stringify(args);
18
+ const d1 = a.difference(b).values().toArray();
19
+ const d2 = b.difference(a).values().toArray();
13
20
 
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>;
21
+ return { d1, d2 };
22
+ }
23
23
 
24
- return memoizedFunction;
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: number | null = null;
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 function formatTimeToHHMMSS(timeString: string): string {
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 parseCSSUrl(s: string) {
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 = 'i') {
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
- if (str.startsWith('r:')) return [new RegExp(str.slice(2), flags)];
13
+ try {
14
+ if (str.startsWith('r:')) return [new RegExp(str.slice(2), flags)];
14
15
 
15
- const regexes = splitWith(str)
16
- .map(
17
- (s) => s.replace(/f:(\w+)/g, (_, w) => `(^|\\ |,)${w}($|\\ |,)`), // full word
18
- )
19
- .map((_) => new RegExp(_, flags));
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
- return regexes;
22
+ return regexes;
23
+ } catch (_) {
24
+ return [];
25
+ }
22
26
  }
23
27
 
24
28
  public hasEvery(str: string) {
File without changes