pervert-monkey 1.0.21 → 1.0.23
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/dist/core/pervertmonkey.core.es.d.ts +15 -14
- package/dist/core/pervertmonkey.core.es.js +1567 -1569
- package/dist/core/pervertmonkey.core.es.js.map +1 -1
- package/dist/core/pervertmonkey.core.umd.js +1567 -1569
- package/dist/core/pervertmonkey.core.umd.js.map +1 -1
- package/dist/userscripts/3hentai.user.js +2 -2
- package/dist/userscripts/camgirlfinder.user.js +2 -2
- package/dist/userscripts/camwhores.user.js +2 -2
- package/dist/userscripts/e-hentai.user.js +2 -2
- package/dist/userscripts/ebalka.user.js +6 -5
- package/dist/userscripts/eporner.user.js +2 -2
- package/dist/userscripts/erome.user.js +7 -3
- package/dist/userscripts/eroprofile.user.js +2 -2
- package/dist/userscripts/javhdporn.user.js +2 -2
- package/dist/userscripts/missav.user.js +2 -2
- package/dist/userscripts/motherless.user.js +2 -2
- package/dist/userscripts/namethatporn.user.js +2 -2
- package/dist/userscripts/nhentai.user.js +2 -2
- package/dist/userscripts/obmenvsem.user.js +2 -2
- package/dist/userscripts/pornhub.user.js +2 -2
- package/dist/userscripts/socialmediagirls.user.js +42 -0
- package/dist/userscripts/spankbang.user.js +2 -2
- package/dist/userscripts/thisvid.user.js +2 -2
- package/dist/userscripts/xhamster.user.js +2 -2
- package/dist/userscripts/xvideos.user.js +2 -2
- package/package.json +2 -2
- package/src/core/data-handler/data-manager.ts +13 -16
- package/src/userscripts/scripts/3hentai.ts +1 -1
- package/src/userscripts/scripts/camgirlfinder.ts +1 -1
- package/src/userscripts/scripts/camwhores.ts +1 -1
- package/src/userscripts/scripts/e-hentai.ts +1 -1
- package/src/userscripts/scripts/ebalka.ts +6 -4
- package/src/userscripts/scripts/eporner.ts +1 -1
- package/src/userscripts/scripts/erome.ts +8 -3
- package/src/userscripts/scripts/eroprofile.ts +1 -1
- package/src/userscripts/scripts/javhdporn.ts +1 -1
- package/src/userscripts/scripts/missav.ts +1 -1
- package/src/userscripts/scripts/motherless.ts +1 -1
- package/src/userscripts/scripts/namethatporn.ts +1 -1
- package/src/userscripts/scripts/nhentai.ts +1 -1
- package/src/userscripts/scripts/obmenvsem.ts +1 -1
- package/src/userscripts/scripts/pornhub.ts +1 -1
- package/src/userscripts/scripts/spankbang.ts +1 -1
- package/src/userscripts/scripts/thisvid.ts +1 -1
- package/src/userscripts/scripts/xhamster.ts +2 -2
- package/src/userscripts/scripts/xvideos.ts +1 -1
- package/src/utils/dom/attributes.ts +54 -0
- package/src/utils/dom/index.ts +4 -165
- package/src/utils/dom/miscellaneous.ts +55 -0
- package/src/utils/dom/selectors.ts +48 -0
- package/src/utils/events/index.ts +2 -2
- package/src/utils/observers/index.ts +16 -11
- package/src/utils/observers/lazy-image-loader.ts +13 -11
- package/src/utils/performance/index.ts +11 -5
- /package/src/utils/dom/{dom-observers.ts → observers.ts} +0 -0
|
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
|
|
|
3
3
|
|
|
4
4
|
export const meta: MonkeyUserScript = {
|
|
5
5
|
name: 'Javhdporn PervertMonkey',
|
|
6
|
-
version: '3.0.
|
|
6
|
+
version: '3.0.16',
|
|
7
7
|
description:
|
|
8
8
|
'Infinite scroll [optional], Filter by Title and Duration, Sort By Duration and Views',
|
|
9
9
|
match: ['https://*.javhdporn.net/*', 'https://*.javhdporn.*/*'],
|
|
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
|
|
|
3
3
|
|
|
4
4
|
export const meta: MonkeyUserScript = {
|
|
5
5
|
name: 'Missav PervertMonkey',
|
|
6
|
-
version: '3.0.
|
|
6
|
+
version: '3.0.15',
|
|
7
7
|
description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
|
|
8
8
|
match: [
|
|
9
9
|
'https://*.missav123.com/*',
|
|
@@ -5,7 +5,7 @@ import { fetchWith, OnHover, replaceElementTag, Tick } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const meta: MonkeyUserScript = {
|
|
7
7
|
name: 'Motherless PervertMonkey',
|
|
8
|
-
version: '5.0.
|
|
8
|
+
version: '5.0.17',
|
|
9
9
|
description:
|
|
10
10
|
'Infinite scroll [optional], Filter by Title, Uploader and Duration, Sort by Duration and Views',
|
|
11
11
|
match: ['https://motherless.com/*'],
|
|
@@ -4,7 +4,7 @@ import { Rules } from '../../core';
|
|
|
4
4
|
|
|
5
5
|
export const meta: MonkeyUserScript = {
|
|
6
6
|
name: 'NameThatPorn PervertMonkey',
|
|
7
|
-
version: '3.0.
|
|
7
|
+
version: '3.0.16',
|
|
8
8
|
description: 'Infinite scroll [optional], Filter by Title, Uploader and Solved/Unsolved',
|
|
9
9
|
match: ['https://namethatporn.com/*'],
|
|
10
10
|
};
|
|
@@ -4,7 +4,7 @@ import { parseHtml } from '../../utils';
|
|
|
4
4
|
|
|
5
5
|
export const meta: MonkeyUserScript = {
|
|
6
6
|
name: 'NHentai PervertMonkey',
|
|
7
|
-
version: '4.0.
|
|
7
|
+
version: '4.0.16',
|
|
8
8
|
description: 'Infinite scroll [optional], Filter by Title',
|
|
9
9
|
match: ['https://*.nhentai.net/*', 'https://*.nhentai.*/*'],
|
|
10
10
|
};
|
|
@@ -5,7 +5,7 @@ import { fetchHtml, parseUrl } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const meta: MonkeyUserScript = {
|
|
7
7
|
name: 'Obmenvsem PervertMonkey',
|
|
8
|
-
version: '1.0.
|
|
8
|
+
version: '1.0.18',
|
|
9
9
|
description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
|
|
10
10
|
match: [
|
|
11
11
|
'https://*.obmenvsem.com/*',
|
|
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
|
|
|
3
3
|
|
|
4
4
|
export const meta: MonkeyUserScript = {
|
|
5
5
|
name: 'PornHub PervertMonkey',
|
|
6
|
-
version: '4.0.
|
|
6
|
+
version: '4.0.16',
|
|
7
7
|
description:
|
|
8
8
|
'Infinite scroll [optional]. Filter by Title, Uploader and Duration. Sort by Duration and Views',
|
|
9
9
|
match: ['https://*.pornhub.com/*'],
|
|
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
|
|
|
3
3
|
|
|
4
4
|
export const meta: MonkeyUserScript = {
|
|
5
5
|
name: 'SpankBang.com PervertMonkey',
|
|
6
|
-
version: '4.0.
|
|
6
|
+
version: '4.0.16',
|
|
7
7
|
description:
|
|
8
8
|
'Infinite scroll [optional]. Filter by Title and Duration. Sort by Duration and Views',
|
|
9
9
|
match: ['https://*.spankbang.com/*', 'https://*.spankbang.*/*'],
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
|
|
22
22
|
export const meta: MonkeyUserScript = {
|
|
23
23
|
name: 'ThisVid.com PervertMonkey',
|
|
24
|
-
version: '8.0.
|
|
24
|
+
version: '8.0.17',
|
|
25
25
|
description:
|
|
26
26
|
'Infinite scroll [optional]. Preview for private videos. Filter by Title, Duration, Quality and Public/Private. Sort by Duration and Views. Private/Public feed of friends uploads. Check access to private vids. Mass friend request button. Sorts messages. Download button 📼',
|
|
27
27
|
match: ['https://*.thisvid.com/*'],
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
|
|
15
15
|
export const meta: MonkeyUserScript = {
|
|
16
16
|
name: 'Xhamster PervertMonkey',
|
|
17
|
-
version: '5.0.
|
|
17
|
+
version: '5.0.18',
|
|
18
18
|
description:
|
|
19
19
|
'Infinite scroll [optional], Filter by Title, Duration and Watched/Unwatched. Sort by Duration and Views',
|
|
20
20
|
match: ['https://*.xhamster.com/*', 'https://*.xhamster.*/*'],
|
|
@@ -162,7 +162,7 @@ function expandMoreVideoPage() {
|
|
|
162
162
|
const observer = new Observer((target) => {
|
|
163
163
|
(target as HTMLButtonElement).click();
|
|
164
164
|
});
|
|
165
|
-
observer.observe(e);
|
|
165
|
+
observer.observe(e as HTMLElement);
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -5,7 +5,7 @@ import { exterminateVideo, OnHover, parseHtml } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const meta: MonkeyUserScript = {
|
|
7
7
|
name: 'XVideos PervertMonkey',
|
|
8
|
-
version: '4.0.
|
|
8
|
+
version: '4.0.18',
|
|
9
9
|
description:
|
|
10
10
|
'Infinite scroll [optional], Filter by Title, Uploader and Duration. Sort by Duration and Views.',
|
|
11
11
|
match: 'https://*.xvideos.com/*',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function copyAttributes<T extends Element = HTMLElement>(target: T, source: T) {
|
|
2
|
+
for (const attr of source.attributes) {
|
|
3
|
+
if (attr.nodeValue) {
|
|
4
|
+
target.setAttribute(attr.nodeName, attr.nodeValue);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function replaceElementTag(e: HTMLElement, tagName: string) {
|
|
10
|
+
const newTagElement = document.createElement(tagName);
|
|
11
|
+
copyAttributes(newTagElement, e);
|
|
12
|
+
|
|
13
|
+
newTagElement.innerHTML = e.innerHTML;
|
|
14
|
+
e.parentNode?.replaceChild(newTagElement, e);
|
|
15
|
+
|
|
16
|
+
return newTagElement;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function removeClassesAndDataAttributes(
|
|
20
|
+
element: HTMLElement,
|
|
21
|
+
keyword: string,
|
|
22
|
+
): void {
|
|
23
|
+
Array.from(element.classList).forEach((className) => {
|
|
24
|
+
if (className.includes(keyword)) {
|
|
25
|
+
element.classList.remove(className);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
30
|
+
if (attr.name.startsWith('data-') && attr.name.includes(keyword)) {
|
|
31
|
+
element.removeAttribute(attr.name);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function areElementsAlike<T extends HTMLElement>(
|
|
37
|
+
a: T,
|
|
38
|
+
b: T,
|
|
39
|
+
options: { id?: boolean; className?: boolean },
|
|
40
|
+
) {
|
|
41
|
+
if (!a || !b) return false;
|
|
42
|
+
|
|
43
|
+
if (options.id && a.id !== b.id) return false;
|
|
44
|
+
|
|
45
|
+
if (options.className) {
|
|
46
|
+
const ca = a.className;
|
|
47
|
+
const cb = b.className;
|
|
48
|
+
if (!(ca.length > cb.length ? ca.includes(cb) : cb.includes(ca))) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return true;
|
|
54
|
+
}
|
package/src/utils/dom/index.ts
CHANGED
|
@@ -1,171 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
waitForElementToAppear,
|
|
6
|
-
waitForElementToDisappear,
|
|
7
|
-
watchDomChangesWithThrottle,
|
|
8
|
-
watchElementChildrenCount,
|
|
9
|
-
} from './dom-observers';
|
|
10
|
-
|
|
11
|
-
export function querySelectorOrSelf<T extends Element = HTMLElement>(
|
|
12
|
-
element: T,
|
|
13
|
-
selector: string,
|
|
14
|
-
): T | null {
|
|
15
|
-
if (element.matches?.(selector)) {
|
|
16
|
-
return element as T;
|
|
17
|
-
}
|
|
18
|
-
return element.querySelector<T>(selector);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function querySelectorLast<T extends Element = HTMLElement>(
|
|
22
|
-
root: ParentNode = document,
|
|
23
|
-
selector: string,
|
|
24
|
-
): T | undefined {
|
|
25
|
-
const nodes = root.querySelectorAll<T>(selector);
|
|
26
|
-
if (nodes.length < 1) {
|
|
27
|
-
return querySelectorOrSelf<T>(root as T, selector) || undefined;
|
|
28
|
-
}
|
|
29
|
-
return nodes[nodes.length - 1];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function querySelectorLastNumber(selector: string, e: ParentNode = document) {
|
|
33
|
-
const text = querySelectorText(e, selector);
|
|
34
|
-
return Number(text.match(/\d+/g)?.pop() || 0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function querySelectorText(e: ParentNode, selector?: string): string {
|
|
38
|
-
if (typeof selector !== 'string') return '';
|
|
39
|
-
const text = querySelectorOrSelf(e as HTMLElement, selector)?.innerText || '';
|
|
40
|
-
return sanitizeStr(text);
|
|
41
|
-
}
|
|
1
|
+
export * from './attributes';
|
|
2
|
+
export * from './miscellaneous';
|
|
3
|
+
export * from './observers';
|
|
4
|
+
export * from './selectors';
|
|
42
5
|
|
|
43
6
|
export function parseHtml(html: string): HTMLElement {
|
|
44
7
|
const parsed = new DOMParser().parseFromString(html, 'text/html').body;
|
|
45
8
|
if (parsed.children.length > 1) return parsed;
|
|
46
9
|
return parsed.firstElementChild as HTMLElement;
|
|
47
10
|
}
|
|
48
|
-
|
|
49
|
-
export function copyAttributes<T extends Element = HTMLElement>(target: T, source: T) {
|
|
50
|
-
for (const attr of source.attributes) {
|
|
51
|
-
if (attr.nodeValue) {
|
|
52
|
-
target.setAttribute(attr.nodeName, attr.nodeValue);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function replaceElementTag(e: HTMLElement, tagName: string) {
|
|
58
|
-
const newTagElement = document.createElement(tagName);
|
|
59
|
-
copyAttributes(newTagElement, e);
|
|
60
|
-
|
|
61
|
-
newTagElement.innerHTML = e.innerHTML;
|
|
62
|
-
e.parentNode?.replaceChild(newTagElement, e);
|
|
63
|
-
|
|
64
|
-
return newTagElement;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function removeClassesAndDataAttributes(
|
|
68
|
-
element: HTMLElement,
|
|
69
|
-
keyword: string,
|
|
70
|
-
): void {
|
|
71
|
-
Array.from(element.classList).forEach((className) => {
|
|
72
|
-
if (className.includes(keyword)) {
|
|
73
|
-
element.classList.remove(className);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
Array.from(element.attributes).forEach((attr) => {
|
|
78
|
-
if (attr.name.startsWith('data-') && attr.name.includes(keyword)) {
|
|
79
|
-
element.removeAttribute(attr.name);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function getCommonParents<T extends HTMLElement>(
|
|
85
|
-
elements: Iterable<T>,
|
|
86
|
-
): HTMLElement[] {
|
|
87
|
-
return Map.groupBy(elements, (e) => e.parentElement)
|
|
88
|
-
.keys()
|
|
89
|
-
.filter((e) => e !== null)
|
|
90
|
-
.toArray();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function findNextSibling<T extends Element = HTMLElement>(e: T) {
|
|
94
|
-
if (e.nextElementSibling) return e.nextElementSibling;
|
|
95
|
-
if (e.parentElement) return findNextSibling(e.parentElement);
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function areElementsAlike<T extends HTMLElement>(
|
|
100
|
-
a: T,
|
|
101
|
-
b: T,
|
|
102
|
-
options: { id?: boolean; className?: boolean },
|
|
103
|
-
) {
|
|
104
|
-
if (!a || !b) return false;
|
|
105
|
-
|
|
106
|
-
if (options.id && a.id !== b.id) return false;
|
|
107
|
-
|
|
108
|
-
if (options.className) {
|
|
109
|
-
const ca = a.className;
|
|
110
|
-
const cb = b.className;
|
|
111
|
-
if (!(ca.length > cb.length ? ca.includes(cb) : cb.includes(ca))) {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function instantiateTemplate(
|
|
120
|
-
sourceSelector: string,
|
|
121
|
-
attributeUpdates: Record<string, string>,
|
|
122
|
-
contentUpdates: Record<string, string>,
|
|
123
|
-
): string {
|
|
124
|
-
const source = document.querySelector(sourceSelector) as HTMLElement;
|
|
125
|
-
|
|
126
|
-
const wrapper = document.createElement('div');
|
|
127
|
-
const clone = source.cloneNode(true);
|
|
128
|
-
wrapper.append(clone);
|
|
129
|
-
|
|
130
|
-
Object.entries(attributeUpdates).forEach(([attrName, attrValue]) => {
|
|
131
|
-
wrapper.querySelectorAll(`[${attrName}]`).forEach((element) => {
|
|
132
|
-
element.setAttribute(attrName, attrValue);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
Object.entries(contentUpdates).forEach(([childSelector, textValue]) => {
|
|
137
|
-
wrapper.querySelectorAll<HTMLElement>(childSelector).forEach((element) => {
|
|
138
|
-
element.innerText = textValue;
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
return wrapper.innerHTML;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function exterminateVideo(video: HTMLVideoElement) {
|
|
146
|
-
video.removeAttribute('src');
|
|
147
|
-
video.load();
|
|
148
|
-
video.remove();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function downloader(options: {
|
|
152
|
-
append?: string;
|
|
153
|
-
after?: string;
|
|
154
|
-
buttonHtml: string;
|
|
155
|
-
doBefore?: () => void;
|
|
156
|
-
}) {
|
|
157
|
-
const btn = parseHtml(options.buttonHtml);
|
|
158
|
-
|
|
159
|
-
if (options.append) document.querySelector(options.append)?.append(btn);
|
|
160
|
-
if (options.after) document.querySelector(options.after)?.after(btn);
|
|
161
|
-
|
|
162
|
-
btn?.addEventListener('click', (e) => {
|
|
163
|
-
e.preventDefault();
|
|
164
|
-
|
|
165
|
-
options.doBefore?.();
|
|
166
|
-
|
|
167
|
-
waitForElementToAppear(document.body, 'video', (video: Element) => {
|
|
168
|
-
window.location.href = video.getAttribute('src') as string;
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { parseHtml, waitForElementToAppear } from '.';
|
|
2
|
+
|
|
3
|
+
export function exterminateVideo(video: HTMLVideoElement) {
|
|
4
|
+
video.removeAttribute('src');
|
|
5
|
+
video.load();
|
|
6
|
+
video.remove();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function downloader(options: {
|
|
10
|
+
append?: string;
|
|
11
|
+
after?: string;
|
|
12
|
+
buttonHtml: string;
|
|
13
|
+
doBefore?: () => void;
|
|
14
|
+
}) {
|
|
15
|
+
const btn = parseHtml(options.buttonHtml);
|
|
16
|
+
|
|
17
|
+
if (options.append) document.querySelector(options.append)?.append(btn);
|
|
18
|
+
if (options.after) document.querySelector(options.after)?.after(btn);
|
|
19
|
+
|
|
20
|
+
btn?.addEventListener('click', (e) => {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
|
|
23
|
+
options.doBefore?.();
|
|
24
|
+
|
|
25
|
+
waitForElementToAppear(document.body, 'video', (video: Element) => {
|
|
26
|
+
window.location.href = video.getAttribute('src') as string;
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function instantiateTemplate(
|
|
32
|
+
sourceSelector: string,
|
|
33
|
+
attributeUpdates: Record<string, string>,
|
|
34
|
+
contentUpdates: Record<string, string>,
|
|
35
|
+
): string {
|
|
36
|
+
const source = document.querySelector(sourceSelector) as HTMLElement;
|
|
37
|
+
|
|
38
|
+
const wrapper = document.createElement('div');
|
|
39
|
+
const clone = source.cloneNode(true);
|
|
40
|
+
wrapper.append(clone);
|
|
41
|
+
|
|
42
|
+
Object.entries(attributeUpdates).forEach(([attrName, attrValue]) => {
|
|
43
|
+
wrapper.querySelectorAll(`[${attrName}]`).forEach((element) => {
|
|
44
|
+
element.setAttribute(attrName, attrValue);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
Object.entries(contentUpdates).forEach(([childSelector, textValue]) => {
|
|
49
|
+
wrapper.querySelectorAll<HTMLElement>(childSelector).forEach((element) => {
|
|
50
|
+
element.innerText = textValue;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return wrapper.innerHTML;
|
|
55
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { sanitizeStr } from '../strings';
|
|
2
|
+
|
|
3
|
+
export function querySelectorOrSelf<T extends Element = HTMLElement>(
|
|
4
|
+
element: T,
|
|
5
|
+
selector: string,
|
|
6
|
+
): T | null {
|
|
7
|
+
if (element.matches?.(selector)) {
|
|
8
|
+
return element as T;
|
|
9
|
+
}
|
|
10
|
+
return element.querySelector<T>(selector);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function querySelectorLast<T extends Element = HTMLElement>(
|
|
14
|
+
root: ParentNode = document,
|
|
15
|
+
selector: string,
|
|
16
|
+
): T | undefined {
|
|
17
|
+
const nodes = root.querySelectorAll<T>(selector);
|
|
18
|
+
if (nodes.length < 1) {
|
|
19
|
+
return querySelectorOrSelf<T>(root as T, selector) || undefined;
|
|
20
|
+
}
|
|
21
|
+
return nodes[nodes.length - 1];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function querySelectorLastNumber(selector: string, e: ParentNode = document) {
|
|
25
|
+
const text = querySelectorText(e, selector);
|
|
26
|
+
return Number(text.match(/\d+/g)?.pop() || 0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function querySelectorText(e: ParentNode, selector?: string): string {
|
|
30
|
+
if (typeof selector !== 'string') return '';
|
|
31
|
+
const text = querySelectorOrSelf(e as HTMLElement, selector)?.innerText || '';
|
|
32
|
+
return sanitizeStr(text);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getCommonParents<T extends HTMLElement>(
|
|
36
|
+
elements: Iterable<T>,
|
|
37
|
+
): HTMLElement[] {
|
|
38
|
+
return Map.groupBy(elements, (e) => e.parentElement)
|
|
39
|
+
.keys()
|
|
40
|
+
.filter((e) => e !== null)
|
|
41
|
+
.toArray();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function findNextSibling<T extends Element = HTMLElement>(e: T) {
|
|
45
|
+
if (e.nextElementSibling) return e.nextElementSibling;
|
|
46
|
+
if (e.parentElement) return findNextSibling(e.parentElement);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export * from './on-hover';
|
|
2
|
+
export * from './tick';
|
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
export { LazyImgLoader } from './lazy-image-loader';
|
|
2
2
|
|
|
3
|
-
export class Observer {
|
|
4
|
-
public observer: IntersectionObserver;
|
|
3
|
+
export class Observer<T extends Element = HTMLElement> {
|
|
5
4
|
private timeout?: number;
|
|
6
|
-
|
|
5
|
+
private observer: IntersectionObserver;
|
|
6
|
+
|
|
7
|
+
constructor(private callback: (entry: T) => void) {
|
|
7
8
|
this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
observe(target:
|
|
11
|
+
public observe(target: T) {
|
|
11
12
|
this.observer.observe(target);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
public unobserve(target: T) {
|
|
15
16
|
this.observer.unobserve(target);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private throttle(target: T, throttleTime: number) {
|
|
20
|
+
this.unobserve(target);
|
|
16
21
|
this.timeout = window.setTimeout(() => this.observer.observe(target), throttleTime);
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
handleIntersection(entries: Iterable<IntersectionObserverEntry>) {
|
|
24
|
+
private handleIntersection(entries: Iterable<IntersectionObserverEntry>) {
|
|
20
25
|
for (const entry of entries) {
|
|
21
26
|
if (entry.isIntersecting) {
|
|
22
|
-
this.callback(entry.target);
|
|
27
|
+
this.callback(entry.target as T);
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
dispose() {
|
|
32
|
+
public dispose() {
|
|
28
33
|
if (this.timeout) clearTimeout(this.timeout);
|
|
29
34
|
this.observer.disconnect();
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
static observeWhile(
|
|
33
|
-
target:
|
|
37
|
+
public static observeWhile<T extends Element = HTMLElement>(
|
|
38
|
+
target: T,
|
|
34
39
|
callback: () => Promise<boolean> | boolean,
|
|
35
40
|
throttleTime: number,
|
|
36
41
|
) {
|
|
37
|
-
const observer = new Observer(async (target:
|
|
42
|
+
const observer = new Observer(async (target: T) => {
|
|
38
43
|
const condition = await callback();
|
|
39
44
|
if (condition) {
|
|
40
45
|
observer.throttle(target, throttleTime);
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import { Observer } from
|
|
1
|
+
import { Observer } from '.';
|
|
2
2
|
|
|
3
|
-
export class LazyImgLoader {
|
|
4
|
-
|
|
5
|
-
private attributeName = 'data-lazy-load';
|
|
3
|
+
export class LazyImgLoader<T extends Element = HTMLElement> {
|
|
4
|
+
private lazyImgObserver: Observer;
|
|
6
5
|
|
|
7
|
-
constructor(
|
|
6
|
+
constructor(
|
|
7
|
+
shouldDelazify: (target: T) => boolean,
|
|
8
|
+
private attributeName = 'data-lazy-orangutan',
|
|
9
|
+
) {
|
|
8
10
|
this.lazyImgObserver = new Observer((target: Element) => {
|
|
9
|
-
if (shouldDelazify(target)) {
|
|
10
|
-
this.
|
|
11
|
+
if (shouldDelazify(target as T)) {
|
|
12
|
+
this.unlazify(target as HTMLImageElement);
|
|
11
13
|
}
|
|
12
14
|
});
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
lazify(
|
|
17
|
+
public lazify(img?: HTMLImageElement, imgSrc?: string) {
|
|
16
18
|
if (!img || !imgSrc) return;
|
|
17
19
|
img.setAttribute(this.attributeName, imgSrc);
|
|
18
20
|
img.src = '';
|
|
19
21
|
this.lazyImgObserver.observe(img);
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
this.lazyImgObserver.
|
|
24
|
+
private unlazify(target: HTMLImageElement) {
|
|
25
|
+
this.lazyImgObserver.unobserve(target);
|
|
24
26
|
target.src = target.getAttribute(this.attributeName) as string;
|
|
25
27
|
target.removeAttribute(this.attributeName);
|
|
26
|
-
}
|
|
28
|
+
}
|
|
27
29
|
}
|
|
@@ -30,15 +30,21 @@ export function runIdleJob<T>(iterator: Iterator<T>, job: (v: T) => void) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function containMutation(container: HTMLElement,
|
|
33
|
+
export async function containMutation(container: HTMLElement, mutation: () => void) {
|
|
34
|
+
const originalContain = container.style.contain;
|
|
34
35
|
container.style.contain = 'content';
|
|
36
|
+
|
|
35
37
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
mutation();
|
|
39
|
+
|
|
40
|
+
await new Promise<void>((resolve) => {
|
|
39
41
|
requestAnimationFrame(() => {
|
|
40
|
-
|
|
42
|
+
requestAnimationFrame(() => {
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
41
45
|
});
|
|
42
46
|
});
|
|
47
|
+
} finally {
|
|
48
|
+
container.style.contain = originalContain;
|
|
43
49
|
}
|
|
44
50
|
}
|
|
File without changes
|