billy-herrington-utils 1.6.0 → 2.0.1
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 +2 -2
- package/dist/billy-herrington-utils.es.js +5908 -327
- package/dist/billy-herrington-utils.es.js.map +1 -1
- package/dist/billy-herrington-utils.umd.js +5912 -331
- package/dist/billy-herrington-utils.umd.js.map +1 -1
- package/dist/index.d.ts +111 -103
- package/package.json +7 -5
- package/src/index.ts +18 -5
- package/src/types/globals.d.ts +0 -2
- package/src/userscripts/data-manager/data-filter.ts +110 -0
- package/src/userscripts/data-manager/index.ts +77 -174
- package/src/userscripts/infinite-scroll/index.ts +72 -71
- package/src/userscripts/pagination-parsing/index.ts +21 -29
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategy.ts +4 -12
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyDataParams.ts +11 -2
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyPathnameParams.ts +29 -2
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategySearchParams.ts +15 -7
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyTrash.ts +9 -12
- package/src/userscripts/pagination-parsing/pagination-strategies/index.ts +1 -1
- package/src/userscripts/pagination-parsing/pagination-utils/index.ts +36 -7
- package/src/userscripts/router/router.ts +71 -0
- package/src/userscripts/rules/index.ts +228 -3
- package/src/userscripts/types/index.ts +19 -0
- package/src/utils/arrays/index.ts +3 -1
- package/src/utils/async/index.ts +22 -6
- package/src/utils/dom/index.ts +35 -68
- package/src/utils/dom/observers.ts +76 -0
- package/src/utils/events/index.ts +9 -2
- package/src/utils/fetch/index.ts +14 -7
- package/src/utils/objects/index.ts +25 -0
- package/src/utils/observers/index.ts +8 -2
- package/src/utils/parsers/index.ts +18 -11
- package/src/utils/strings/index.ts +5 -5
- package/src/utils/strings/regexes.ts +31 -0
- package/src/utils/userscript/index.ts +10 -0
- package/src/userscripts/jabroni-outfit-wrap/index.ts +0 -40
|
@@ -1,192 +1,90 @@
|
|
|
1
|
+
import type { StoreState } from 'jabroni-outfit';
|
|
1
2
|
import { LazyImgLoader } from '../../utils/observers';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
filterPublic: boolean;
|
|
6
|
-
filterPrivate: boolean;
|
|
7
|
-
filterHD: boolean;
|
|
8
|
-
filterDuration: boolean;
|
|
9
|
-
filterDurationFrom: number;
|
|
10
|
-
filterDurationTo: number;
|
|
11
|
-
filterExclude: boolean;
|
|
12
|
-
filterExcludeWords: string;
|
|
13
|
-
filterInclude: boolean;
|
|
14
|
-
filterIncludeWords: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface FilterResult {
|
|
18
|
-
tag: string;
|
|
19
|
-
condition: boolean;
|
|
20
|
-
}
|
|
3
|
+
import { assignGlobals } from '../../utils/userscript';
|
|
4
|
+
import type { RulesGlobal } from '../rules';
|
|
5
|
+
import { DataFilter, type FilterFunction } from './data-filter';
|
|
21
6
|
|
|
22
|
-
type
|
|
23
|
-
type FilterFunction = (v: FilterInput) => FilterResult;
|
|
7
|
+
export type DataElement = Record<string, string | number | boolean | HTMLElement>;
|
|
24
8
|
|
|
25
|
-
class
|
|
26
|
-
|
|
9
|
+
export class DataManager {
|
|
10
|
+
private data = new Map<string, DataElement>();
|
|
11
|
+
private lazyImgLoader = new LazyImgLoader(
|
|
12
|
+
(target: Element) => !this.isFiltered(target as HTMLElement),
|
|
13
|
+
);
|
|
14
|
+
private dataFilter: DataFilter;
|
|
27
15
|
|
|
28
16
|
constructor(
|
|
29
|
-
private rules:
|
|
30
|
-
private state:
|
|
17
|
+
private rules: RulesGlobal,
|
|
18
|
+
private state: StoreState,
|
|
31
19
|
) {
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
const methods = Object.getOwnPropertyNames(this);
|
|
35
|
-
this.filters = methods.reduce((acc: { [key: string]: () => FilterFunction }, k) => {
|
|
36
|
-
if (k in this.state) {
|
|
37
|
-
acc[k] = this[k as keyof DataFilter] as unknown as () => FilterFunction;
|
|
38
|
-
GM_addStyle(`.filter-${k.toLowerCase().slice(6)} { display: none !important; }`);
|
|
39
|
-
}
|
|
40
|
-
return acc;
|
|
41
|
-
}, {});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
filterPublic = (): FilterFunction => {
|
|
45
|
-
return (v: FilterInput) => {
|
|
46
|
-
const isPublic = !this.rules.isPrivate(v.element as HTMLElement);
|
|
47
|
-
return {
|
|
48
|
-
condition: this.state.filterPublic && isPublic,
|
|
49
|
-
tag: 'filter-public',
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
filterPrivate = (): FilterFunction => {
|
|
55
|
-
return (v: FilterInput) => {
|
|
56
|
-
const isPrivate = this.rules.isPrivate(v.element as HTMLElement);
|
|
57
|
-
return {
|
|
58
|
-
condition: this.state.filterPrivate && isPrivate,
|
|
59
|
-
tag: 'filter-private',
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
filterHD = (): FilterFunction => {
|
|
65
|
-
return (v: FilterInput) => {
|
|
66
|
-
const isHD = this.rules.isHD(v.element as HTMLElement);
|
|
67
|
-
return {
|
|
68
|
-
condition: this.state.filterHD && isHD,
|
|
69
|
-
tag: 'filter-hd',
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
filterDuration = (): FilterFunction => {
|
|
75
|
-
return (v: FilterInput) => {
|
|
76
|
-
const notInRange =
|
|
77
|
-
(v.duration as number) < this.state.filterDurationFrom ||
|
|
78
|
-
(v.duration as number) > this.state.filterDurationTo;
|
|
79
|
-
return {
|
|
80
|
-
condition: this.state.filterDuration && notInRange,
|
|
81
|
-
tag: 'filter-duration',
|
|
82
|
-
};
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
filterExclude = (): FilterFunction => {
|
|
87
|
-
const tags = DataManager.filterDSLToRegex(this.state.filterExcludeWords);
|
|
88
|
-
return (v: FilterInput) => {
|
|
89
|
-
const containTags = tags.some((tag) => tag.test(v.title as string));
|
|
90
|
-
return {
|
|
91
|
-
condition: this.state.filterExclude && containTags,
|
|
92
|
-
tag: 'filter-exclude',
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
filterInclude = (): FilterFunction => {
|
|
98
|
-
const tags = DataManager.filterDSLToRegex(this.state.filterIncludeWords);
|
|
99
|
-
return (v: FilterInput) => {
|
|
100
|
-
const containTagsNot = tags.some((tag) => !tag.test(v.title as string));
|
|
101
|
-
return {
|
|
102
|
-
condition: this.state.filterInclude && containTagsNot,
|
|
103
|
-
tag: 'filter-include',
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
}
|
|
20
|
+
this.dataFilter = new DataFilter(rules, state);
|
|
108
21
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
getThumbData: (thumbElement: HTMLElement) => { title: string; duration: number };
|
|
113
|
-
getThumbImgData: (thumbElement: HTMLElement) => { img: HTMLElement; imgSrc: string };
|
|
114
|
-
container: HTMLElement;
|
|
115
|
-
isPrivate: (element: HTMLElement) => boolean;
|
|
116
|
-
isHD: (element: HTMLElement) => boolean;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export class DataManager {
|
|
120
|
-
private rules: IRules;
|
|
121
|
-
private state: DataFilterState;
|
|
122
|
-
private data: Map<string, FilterInput>;
|
|
123
|
-
private lazyImgLoader: LazyImgLoader;
|
|
124
|
-
public dataFilters: { [key: string]: () => FilterFunction };
|
|
125
|
-
|
|
126
|
-
constructor(rules: IRules, state: DataFilterState) {
|
|
127
|
-
this.rules = rules;
|
|
128
|
-
this.state = state;
|
|
129
|
-
this.data = new Map();
|
|
130
|
-
this.lazyImgLoader = new LazyImgLoader(
|
|
131
|
-
(target: Element) => !this.isFiltered(target as HTMLElement),
|
|
132
|
-
);
|
|
133
|
-
this.dataFilters = new DataFilter(rules, state).filters;
|
|
134
|
-
|
|
135
|
-
const targets = [window, (globalThis as any).unsafeWindow].filter(Boolean);
|
|
136
|
-
targets.forEach((w: any) => {
|
|
137
|
-
Object.assign(w, {
|
|
138
|
-
sortByDuration: () => this.sort('duration'),
|
|
139
|
-
sortByViews: () => this.sort('view'),
|
|
140
|
-
});
|
|
22
|
+
assignGlobals({
|
|
23
|
+
sortByDuration: () => this.sortBy('duration'),
|
|
24
|
+
sortByViews: () => this.sortBy('view'),
|
|
141
25
|
});
|
|
142
26
|
}
|
|
143
27
|
|
|
144
|
-
|
|
145
|
-
const toFullWord = (w: string) => `(^|\\ )${w}($|\\ )`;
|
|
146
|
-
const str_ = str.replace(/f:(\w+)/g, (_, w) => toFullWord(w));
|
|
147
|
-
return stringToWords(str_).map((expr: string) => new RegExp(expr, 'i'));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
isFiltered(el: HTMLElement): boolean {
|
|
28
|
+
public isFiltered(el: HTMLElement): boolean {
|
|
151
29
|
return el.className.includes('filtered');
|
|
152
30
|
}
|
|
153
31
|
|
|
154
|
-
applyFilters = (filters: { [key: string]: boolean }, offset = 0): void => {
|
|
32
|
+
public applyFilters = (filters: { [key: string]: boolean } = {}, offset = 0): void => {
|
|
155
33
|
const filtersToApply = Object.keys(filters)
|
|
156
|
-
.filter((k) =>
|
|
157
|
-
.map((k) => this.
|
|
34
|
+
.filter((k) => this.dataFilter.filters.has(k))
|
|
35
|
+
.map((k) => this.dataFilter.filters.get(k) as () => FilterFunction);
|
|
158
36
|
|
|
159
37
|
if (filtersToApply.length === 0) return;
|
|
160
38
|
|
|
161
|
-
const
|
|
162
|
-
let
|
|
163
|
-
|
|
164
|
-
|
|
39
|
+
const iterator = this.data.values();
|
|
40
|
+
let currentIndex = 0;
|
|
41
|
+
|
|
42
|
+
const runBatch = (deadline: IdleDeadline) => {
|
|
43
|
+
while (currentIndex < offset) {
|
|
44
|
+
const skip = iterator.next();
|
|
45
|
+
if (skip.done) return;
|
|
46
|
+
currentIndex++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const updates: { e: HTMLElement; tag: string; condition: boolean }[] = [];
|
|
50
|
+
|
|
51
|
+
while (deadline.timeRemaining() > 0) {
|
|
52
|
+
const { value, done } = iterator.next();
|
|
53
|
+
if (done) break;
|
|
54
|
+
|
|
165
55
|
for (const f of filtersToApply) {
|
|
166
|
-
const { tag, condition } = f(
|
|
167
|
-
updates.push(
|
|
56
|
+
const { tag, condition } = f()(value);
|
|
57
|
+
updates.push({ e: value.element as HTMLElement, tag, condition });
|
|
168
58
|
}
|
|
169
59
|
}
|
|
170
|
-
}
|
|
171
60
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
61
|
+
if (updates.length > 0) {
|
|
62
|
+
requestAnimationFrame(() => {
|
|
63
|
+
updates.forEach((u) => {
|
|
64
|
+
u.e.classList.toggle(u.tag, u.condition);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (currentIndex < this.data.size) {
|
|
70
|
+
requestIdleCallback(runBatch);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
requestIdleCallback(runBatch);
|
|
177
75
|
};
|
|
178
76
|
|
|
179
|
-
filterAll = (offset?: number): void => {
|
|
77
|
+
public filterAll = (offset?: number): void => {
|
|
180
78
|
const filters = Object.assign(
|
|
181
79
|
{},
|
|
182
|
-
...Object.keys(this.
|
|
183
|
-
[f]: this.state[f as keyof
|
|
80
|
+
...Object.keys(this.dataFilter.filters).map((f) => ({
|
|
81
|
+
[f]: this.state[f as keyof StoreState],
|
|
184
82
|
})),
|
|
185
83
|
);
|
|
186
84
|
this.applyFilters(filters, offset);
|
|
187
85
|
};
|
|
188
86
|
|
|
189
|
-
parseData = (
|
|
87
|
+
public parseData = (
|
|
190
88
|
html: HTMLElement,
|
|
191
89
|
container?: HTMLElement,
|
|
192
90
|
removeDuplicates = false,
|
|
@@ -194,10 +92,12 @@ export class DataManager {
|
|
|
194
92
|
): void => {
|
|
195
93
|
const thumbs = this.rules.getThumbs(html);
|
|
196
94
|
const data_offset = this.data.size;
|
|
95
|
+
const fragment = document.createDocumentFragment();
|
|
96
|
+
const parent = container || this.rules.container;
|
|
197
97
|
|
|
198
98
|
for (const thumbElement of thumbs) {
|
|
199
99
|
const url = this.rules.getThumbUrl(thumbElement);
|
|
200
|
-
if (!url || this.data.has(url)) {
|
|
100
|
+
if (!url || this.data.has(url) || parent.contains(thumbElement)) {
|
|
201
101
|
if (removeDuplicates) thumbElement.remove();
|
|
202
102
|
continue;
|
|
203
103
|
}
|
|
@@ -207,32 +107,35 @@ export class DataManager {
|
|
|
207
107
|
|
|
208
108
|
if (shouldLazify) {
|
|
209
109
|
const { img, imgSrc } = this.rules.getThumbImgData(thumbElement);
|
|
210
|
-
this.lazyImgLoader.lazify(thumbElement, img
|
|
110
|
+
this.lazyImgLoader.lazify(thumbElement, img, imgSrc);
|
|
211
111
|
}
|
|
212
112
|
|
|
213
|
-
|
|
214
|
-
if (!parent.contains(thumbElement)) parent.appendChild(thumbElement);
|
|
113
|
+
fragment.append(thumbElement);
|
|
215
114
|
}
|
|
216
115
|
|
|
116
|
+
requestAnimationFrame(() => {
|
|
117
|
+
parent.appendChild(fragment);
|
|
118
|
+
});
|
|
119
|
+
|
|
217
120
|
this.filterAll(data_offset);
|
|
218
121
|
};
|
|
219
122
|
|
|
220
|
-
|
|
123
|
+
public sortBy<K extends keyof DataElement>(key: K): void {
|
|
221
124
|
if (this.data.size < 2) return;
|
|
222
125
|
|
|
223
|
-
const sorted = Array.from(this.data.
|
|
224
|
-
|
|
225
|
-
(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
});
|
|
126
|
+
const sorted: DataElement[] = Array.from(this.data.values()).sort(
|
|
127
|
+
(a: DataElement, b: DataElement) => {
|
|
128
|
+
return (a[key] as number) - (b[key] as number);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
229
131
|
|
|
230
|
-
const container = (
|
|
231
|
-
|
|
132
|
+
const container = (sorted[0].element as HTMLElement).parentElement as HTMLElement;
|
|
133
|
+
container.style.visibility = 'hidden';
|
|
232
134
|
|
|
233
135
|
sorted.forEach((s) => {
|
|
234
|
-
|
|
235
|
-
container.append(e);
|
|
136
|
+
container.append(s.element as HTMLElement);
|
|
236
137
|
});
|
|
138
|
+
|
|
139
|
+
container.style.visibility = 'visible';
|
|
237
140
|
}
|
|
238
141
|
}
|
|
@@ -1,64 +1,40 @@
|
|
|
1
|
+
import type { JabronioStore } from 'jabroni-outfit';
|
|
1
2
|
import { fetchHtml } from '../../utils/fetch';
|
|
2
3
|
import { Observer } from '../../utils/observers';
|
|
4
|
+
import type { RulesGlobal } from '../rules';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
writeHistory?: boolean;
|
|
8
|
-
paginationOffset: number;
|
|
9
|
-
paginationLast: number;
|
|
10
|
-
paginationElement: HTMLElement;
|
|
11
|
-
paginationUrlGenerator: (offset: number) => string;
|
|
12
|
-
parseData: (document: HTMLElement) => void;
|
|
13
|
-
intersectionObservable?: HTMLElement;
|
|
14
|
-
alternativeGenerator?: () => OffsetGenerator;
|
|
15
|
-
}
|
|
6
|
+
type InfiniteScrollerOptions = Pick<InfiniteScroller, 'rules'> & Partial<InfiniteScroller>;
|
|
7
|
+
type GeneratorResult = { url: string; offset: number };
|
|
8
|
+
export type OffsetGenerator = Generator<GeneratorResult> | AsyncGenerator<GeneratorResult>;
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
export class InfiniteScroller {
|
|
11
|
+
public enabled = true;
|
|
12
|
+
public delay = 200;
|
|
13
|
+
public paginationOffset = 1;
|
|
14
|
+
public writeHistory = false;
|
|
15
|
+
public parseData?: (document: HTMLElement) => void;
|
|
16
|
+
public rules: RulesGlobal;
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
private observer?: Observer;
|
|
19
|
+
private paginationGenerator: OffsetGenerator;
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
parseData,
|
|
42
|
-
alternativeGenerator,
|
|
43
|
-
intersectionObservable,
|
|
44
|
-
}: IInfiniteScroller) {
|
|
45
|
-
this.enabled = enabled;
|
|
46
|
-
this.delay = delay;
|
|
47
|
-
this.writeHistory = writeHistory;
|
|
48
|
-
this.paginationOffset = paginationOffset;
|
|
49
|
-
this.paginationLast = paginationLast;
|
|
50
|
-
this.parseData = parseData;
|
|
51
|
-
|
|
52
|
-
this.paginationGenerator =
|
|
53
|
-
alternativeGenerator?.() ??
|
|
54
|
-
InfiniteScroller.createPaginationGenerator(
|
|
55
|
-
paginationOffset,
|
|
56
|
-
paginationLast,
|
|
57
|
-
paginationUrlGenerator,
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const observable = intersectionObservable || paginationElement;
|
|
61
|
-
Observer.observeWhile(observable, this.generatorConsumer, this.delay);
|
|
21
|
+
constructor(options: InfiniteScrollerOptions) {
|
|
22
|
+
this.rules = options.rules;
|
|
23
|
+
this.paginationOffset = this.rules.paginationStrategy.getPaginationOffset();
|
|
24
|
+
Object.assign(this, options);
|
|
25
|
+
|
|
26
|
+
this.paginationGenerator = this.createPaginationGenerator();
|
|
27
|
+
this.setObserver(this.rules.observable);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public dispose() {
|
|
31
|
+
if (this.observer) this.observer.dispose();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public setObserver(observable: HTMLElement) {
|
|
35
|
+
if (this.observer) this.observer.dispose();
|
|
36
|
+
this.observer = Observer.observeWhile(observable, this.generatorConsumer, this.delay);
|
|
37
|
+
return this;
|
|
62
38
|
}
|
|
63
39
|
|
|
64
40
|
private onScrollCBs: Array<(scroller: InfiniteScroller) => void> = [];
|
|
@@ -79,27 +55,52 @@ export class InfiniteScroller {
|
|
|
79
55
|
if (!this.enabled) return false;
|
|
80
56
|
const { value: { url, offset } = {}, done } = await this.paginationGenerator.next();
|
|
81
57
|
if (!done) {
|
|
82
|
-
|
|
83
|
-
const prevScrollPos = document.documentElement.scrollTop;
|
|
84
|
-
this.paginationOffset = offset;
|
|
85
|
-
this.parseData(nextPageHTML);
|
|
86
|
-
this._onScroll();
|
|
87
|
-
window.scrollTo(0, prevScrollPos);
|
|
88
|
-
if (this.writeHistory) {
|
|
89
|
-
history.replaceState({}, '', url);
|
|
90
|
-
}
|
|
58
|
+
await this.doScroll(url, offset);
|
|
91
59
|
}
|
|
92
60
|
return !done;
|
|
93
61
|
};
|
|
94
62
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
63
|
+
async doScroll(url: string, offset: number) {
|
|
64
|
+
const nextPageHTML = await fetchHtml(url);
|
|
65
|
+
const prevScrollPos = document.documentElement.scrollTop;
|
|
66
|
+
this.paginationOffset = Math.max(this.paginationOffset, offset);
|
|
67
|
+
this.parseData?.(nextPageHTML);
|
|
68
|
+
this._onScroll();
|
|
69
|
+
window.scrollTo(0, prevScrollPos);
|
|
70
|
+
if (this.writeHistory) {
|
|
71
|
+
history.replaceState({}, '', url);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private *createPaginationGenerator(): OffsetGenerator {
|
|
76
|
+
const curPage = this.rules.paginationStrategy.getPaginationOffset();
|
|
77
|
+
const lastPage = this.rules.paginationStrategy.getPaginationLast();
|
|
78
|
+
for (let offset = curPage + 1; offset <= lastPage; offset++) {
|
|
79
|
+
const url = this.rules.paginationStrategy.getPaginationUrlGenerator()(offset);
|
|
102
80
|
yield { url, offset };
|
|
103
81
|
}
|
|
104
82
|
}
|
|
83
|
+
|
|
84
|
+
static create(
|
|
85
|
+
store: JabronioStore,
|
|
86
|
+
rules: RulesGlobal,
|
|
87
|
+
parseData: (document: HTMLElement) => void,
|
|
88
|
+
) {
|
|
89
|
+
const enabled = store.state.infiniteScrollEnabled as boolean;
|
|
90
|
+
|
|
91
|
+
store.state.$paginationLast = rules.paginationStrategy.getPaginationLast();
|
|
92
|
+
|
|
93
|
+
const infiniteScroller = new InfiniteScroller({ enabled, parseData, rules }).onScroll(
|
|
94
|
+
({ paginationOffset }) => {
|
|
95
|
+
store.state.$aginationOffset = paginationOffset;
|
|
96
|
+
},
|
|
97
|
+
true,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
store.subscribe(() => {
|
|
101
|
+
infiniteScroller.enabled = store.state.infiniteScrollEnabled as boolean;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return infiniteScroller;
|
|
105
|
+
}
|
|
105
106
|
}
|
|
@@ -1,53 +1,39 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type IPaginationStrategy,
|
|
3
2
|
PaginationStrategy,
|
|
4
3
|
PaginationStrategyDataParams,
|
|
5
4
|
PaginationStrategyPathnameParams,
|
|
6
5
|
PaginationStrategySearchParams,
|
|
7
6
|
} from './pagination-strategies';
|
|
8
|
-
import { getPaginationLinks
|
|
7
|
+
import { getPaginationLinks } from './pagination-utils';
|
|
9
8
|
|
|
10
|
-
export function getPaginationStrategy(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
searchParamSelector,
|
|
16
|
-
} = options;
|
|
9
|
+
export function getPaginationStrategy(
|
|
10
|
+
options: Partial<PaginationStrategy>,
|
|
11
|
+
): PaginationStrategy {
|
|
12
|
+
const _paginationStrategy = new PaginationStrategy(options);
|
|
13
|
+
const pagination = _paginationStrategy.getPaginationElement();
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
Object.assign(options, { ..._paginationStrategy });
|
|
16
|
+
const { url, searchParamSelector } = options;
|
|
19
17
|
|
|
20
18
|
if (!pagination) {
|
|
21
19
|
console.error('Found No Pagination');
|
|
22
|
-
return
|
|
20
|
+
return _paginationStrategy;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
const pageLinks = getPaginationLinks(pagination, url).map((l) => new URL(l));
|
|
26
24
|
|
|
27
25
|
console.log({ pageLinks: pageLinks.map((l) => l.href) });
|
|
28
26
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
if (dataParamLinks.length > 0) {
|
|
32
|
-
console.log('PaginationStrategyDataParams', dataParamLinks);
|
|
27
|
+
const selectStrategy = (): typeof PaginationStrategy => {
|
|
28
|
+
if (PaginationStrategyDataParams.testLinks(pagination)) {
|
|
33
29
|
return PaginationStrategyDataParams;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
if (
|
|
37
|
-
const l = pageLinks
|
|
38
|
-
.filter((h) => PaginationStrategySearchParams.checkLink(h, searchParamSelector))
|
|
39
|
-
.map((h) => h.href);
|
|
40
|
-
console.log('PaginationStrategySearchParams', l);
|
|
32
|
+
if (PaginationStrategySearchParams.testLinks(pageLinks, searchParamSelector)) {
|
|
41
33
|
return PaginationStrategySearchParams;
|
|
42
34
|
}
|
|
43
35
|
|
|
44
|
-
if (
|
|
45
|
-
const pathnameMatched = pageLinks.filter((h) => /\/(page\/)?\d+\/?$/.test(h.pathname));
|
|
46
|
-
console.log(
|
|
47
|
-
'PaginationStrategyPathnameParams',
|
|
48
|
-
pathnameMatched.map((h) => h.href),
|
|
49
|
-
);
|
|
50
|
-
options.url = upgradePathname(parseURL(url), pathnameMatched);
|
|
36
|
+
if (PaginationStrategyPathnameParams.testLinks(pageLinks, options)) {
|
|
51
37
|
return PaginationStrategyPathnameParams;
|
|
52
38
|
}
|
|
53
39
|
|
|
@@ -55,9 +41,15 @@ export function getPaginationStrategy(options: IPaginationStrategy): PaginationS
|
|
|
55
41
|
return PaginationStrategy;
|
|
56
42
|
};
|
|
57
43
|
|
|
58
|
-
const
|
|
44
|
+
const PaginationStrategyConstructor = selectStrategy();
|
|
45
|
+
const paginationStrategy = new PaginationStrategyConstructor(options);
|
|
59
46
|
|
|
60
|
-
console.log(
|
|
47
|
+
console.log(
|
|
48
|
+
'paginationStrategy:',
|
|
49
|
+
PaginationStrategyConstructor.name,
|
|
50
|
+
'\n',
|
|
51
|
+
paginationStrategy,
|
|
52
|
+
);
|
|
61
53
|
|
|
62
54
|
return paginationStrategy;
|
|
63
55
|
}
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { parseURL } from '../pagination-utils';
|
|
2
2
|
|
|
3
|
-
export interface IPaginationStrategy {
|
|
4
|
-
url?: URL | Location | string;
|
|
5
|
-
doc?: Document | HTMLElement;
|
|
6
|
-
paginationSelector?: string;
|
|
7
|
-
fixPaginationLast?: (n: number, offset?: number) => number;
|
|
8
|
-
pathnameSelector?: RegExp;
|
|
9
|
-
searchParamSelector?: string;
|
|
10
|
-
offsetMin?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
3
|
export class PaginationStrategy {
|
|
14
4
|
public doc = document;
|
|
15
5
|
public url: URL;
|
|
16
6
|
public paginationSelector = '.pagination';
|
|
17
7
|
public searchParamSelector = 'page';
|
|
8
|
+
public static _pathnameSelector = /\/(page\/)?\d+\/?$/;
|
|
18
9
|
public pathnameSelector = /\/(\d+)\/?$/;
|
|
10
|
+
public dataparamSelector = '[data-parameters *= from]';
|
|
19
11
|
public fixPaginationLast?: (n: number, offset?: number) => number;
|
|
20
12
|
public offsetMin = 1;
|
|
21
13
|
|
|
22
|
-
constructor(options?:
|
|
14
|
+
constructor(options?: Partial<PaginationStrategy>) {
|
|
23
15
|
if (options) {
|
|
24
16
|
Object.entries(options).forEach(([k, v]) => {
|
|
25
17
|
Object.assign(this, { [k]: v });
|
|
@@ -30,7 +22,7 @@ export class PaginationStrategy {
|
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
getPaginationElement() {
|
|
33
|
-
return this.doc.querySelector(this.paginationSelector);
|
|
25
|
+
return this.doc.querySelector<HTMLElement>(this.paginationSelector);
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
get hasPagination() {
|
package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyDataParams.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { PaginationStrategy } from './PaginationStrategy';
|
|
|
3
3
|
|
|
4
4
|
export class PaginationStrategyDataParams extends PaginationStrategy {
|
|
5
5
|
getPaginationLast() {
|
|
6
|
-
const links = this.getPaginationElement()?.querySelectorAll(
|
|
6
|
+
const links = this.getPaginationElement()?.querySelectorAll(this.dataparamSelector);
|
|
7
7
|
const pages = Array.from(links || [], (l) => {
|
|
8
8
|
const p = l.getAttribute('data-parameters');
|
|
9
9
|
const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
|
|
@@ -31,7 +31,9 @@ export class PaginationStrategyDataParams extends PaginationStrategy {
|
|
|
31
31
|
'a[data-block-id][data-parameters]',
|
|
32
32
|
);
|
|
33
33
|
const block_id = parametersElement?.getAttribute('data-block-id') || '';
|
|
34
|
-
const parameters = parseDataParams(
|
|
34
|
+
const parameters = parseDataParams(
|
|
35
|
+
parametersElement?.getAttribute('data-parameters') || '',
|
|
36
|
+
);
|
|
35
37
|
|
|
36
38
|
const attrs: Record<string, string> = {
|
|
37
39
|
block_id,
|
|
@@ -54,4 +56,11 @@ export class PaginationStrategyDataParams extends PaginationStrategy {
|
|
|
54
56
|
|
|
55
57
|
return paginationUrlGenerator;
|
|
56
58
|
}
|
|
59
|
+
|
|
60
|
+
static testLinks(doc: HTMLElement | Document = document) {
|
|
61
|
+
const dataParamLinks = Array.from(
|
|
62
|
+
doc.querySelectorAll<HTMLElement>('[data-parameters *= from]'),
|
|
63
|
+
);
|
|
64
|
+
return dataParamLinks.length > 0;
|
|
65
|
+
}
|
|
57
66
|
}
|
package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyPathnameParams.ts
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
|
-
import { getPaginationLinks } from '../pagination-utils';
|
|
1
|
+
import { getPaginationLinks, parseURL, upgradePathname } from '../pagination-utils';
|
|
2
2
|
import { PaginationStrategy } from './PaginationStrategy';
|
|
3
3
|
|
|
4
4
|
export class PaginationStrategyPathnameParams extends PaginationStrategy {
|
|
5
5
|
extractPage = (a: HTMLAnchorElement | Location | string): number => {
|
|
6
6
|
const href = typeof a === 'string' ? a : a.href;
|
|
7
7
|
const { pathname } = new URL(href, this.doc.baseURI || this.url.origin);
|
|
8
|
-
return parseInt(
|
|
8
|
+
return parseInt(
|
|
9
|
+
pathname.match(this.pathnameSelector)?.pop() || this.offsetMin.toString(),
|
|
10
|
+
);
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
static checkLink(
|
|
14
|
+
link: URL,
|
|
15
|
+
pathnameSelector: RegExp = PaginationStrategy._pathnameSelector,
|
|
16
|
+
): boolean {
|
|
17
|
+
return pathnameSelector.test(link.pathname);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static testLinks(links: URL[], options: Partial<PaginationStrategy>): boolean {
|
|
21
|
+
const result = links.some((h) =>
|
|
22
|
+
PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (result) {
|
|
26
|
+
const pathnamesMatched = links.filter((h) =>
|
|
27
|
+
PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector),
|
|
28
|
+
);
|
|
29
|
+
options.url = upgradePathname(
|
|
30
|
+
parseURL(options.url as unknown as string),
|
|
31
|
+
pathnamesMatched,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
11
38
|
getPaginationLast() {
|
|
12
39
|
const links = getPaginationLinks(
|
|
13
40
|
(this.getPaginationElement() || document) as HTMLElement,
|