wikiparser-node 0.8.1-m → 0.9.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 +39 -0
- package/config/moegirl.json +1 -0
- package/i18n/zh-hans.json +44 -0
- package/i18n/zh-hant.json +44 -0
- package/index.js +264 -10
- package/lib/element.js +507 -33
- package/lib/node.js +550 -6
- package/lib/ranges.js +130 -0
- package/lib/text.js +111 -41
- package/lib/title.js +28 -5
- package/mixin/attributeParent.js +117 -0
- package/mixin/fixedToken.js +40 -0
- package/mixin/hidden.js +3 -0
- package/mixin/singleLine.js +31 -0
- package/mixin/sol.js +54 -0
- package/package.json +9 -8
- package/parser/brackets.js +9 -2
- package/parser/commentAndExt.js +3 -5
- package/parser/converter.js +1 -0
- package/parser/externalLinks.js +1 -0
- package/parser/hrAndDoubleUnderscore.js +1 -0
- package/parser/html.js +1 -0
- package/parser/links.js +6 -5
- package/parser/list.js +1 -0
- package/parser/magicLinks.js +5 -4
- package/parser/quotes.js +1 -0
- package/parser/selector.js +177 -0
- package/parser/table.js +1 -0
- package/src/arg.js +123 -5
- package/src/atom/hidden.js +2 -0
- package/src/atom/index.js +17 -0
- package/src/attribute.js +191 -8
- package/src/attributes.js +311 -8
- package/src/charinsert.js +97 -0
- package/src/converter.js +108 -2
- package/src/converterFlags.js +190 -3
- package/src/converterRule.js +185 -4
- package/src/extLink.js +122 -2
- package/src/gallery.js +59 -11
- package/src/hasNowiki/index.js +12 -0
- package/src/hasNowiki/pre.js +12 -0
- package/src/heading.js +57 -6
- package/src/html.js +133 -12
- package/src/imageParameter.js +232 -38
- package/src/imagemap.js +65 -6
- package/src/imagemapLink.js +14 -2
- package/src/index.js +537 -8
- package/src/link/category.js +32 -1
- package/src/link/file.js +173 -11
- package/src/link/galleryImage.js +63 -5
- package/src/link/index.js +268 -9
- package/src/magicLink.js +92 -11
- package/src/nested/choose.js +1 -0
- package/src/nested/combobox.js +1 -0
- package/src/nested/index.js +31 -7
- package/src/nested/references.js +1 -0
- package/src/nowiki/comment.js +27 -3
- package/src/nowiki/dd.js +47 -1
- package/src/nowiki/doubleUnderscore.js +31 -1
- package/src/nowiki/hr.js +20 -1
- package/src/nowiki/index.js +25 -3
- package/src/nowiki/list.js +5 -2
- package/src/nowiki/noinclude.js +14 -0
- package/src/nowiki/quote.js +17 -3
- package/src/onlyinclude.js +26 -1
- package/src/paramTag/index.js +27 -4
- package/src/paramTag/inputbox.js +4 -1
- package/src/parameter.js +150 -8
- package/src/syntax.js +68 -0
- package/src/table/index.js +941 -4
- package/src/table/td.js +229 -10
- package/src/table/tr.js +249 -4
- package/src/tagPair/ext.js +36 -9
- package/src/tagPair/include.js +24 -0
- package/src/tagPair/index.js +51 -2
- package/src/transclude.js +547 -29
- package/tool/index.js +1202 -0
- package/util/debug.js +73 -0
- package/util/lint.js +8 -7
- package/util/string.js +67 -1
- package/config/minimum.json +0 -142
package/tool/index.js
ADDED
|
@@ -0,0 +1,1202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {typeError} = require('../util/debug'),
|
|
4
|
+
{text} = require('../util/string'),
|
|
5
|
+
{isPlainObject} = require('../util/base'),
|
|
6
|
+
Parser = require('..'),
|
|
7
|
+
AstNode = require('../lib/node'),
|
|
8
|
+
Token = require('../src'),
|
|
9
|
+
AttributesToken = require('../src/attributes');
|
|
10
|
+
|
|
11
|
+
const /** @type {WeakMap<Token, Record<string, *>>} */ cache = new WeakMap();
|
|
12
|
+
|
|
13
|
+
/** Token集合 */
|
|
14
|
+
class TokenCollection {
|
|
15
|
+
/** @type {AstNode[]} */ array = [];
|
|
16
|
+
/** @type {Set<Token>} */ #roots = new Set();
|
|
17
|
+
/** @type {TokenCollection} */ prevObject;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 生成匹配函数
|
|
21
|
+
* @param {string} method
|
|
22
|
+
* @param {string|AstNode|Iterable<AstNode>} selector
|
|
23
|
+
* @returns {(token: AstNode) => boolean}
|
|
24
|
+
*/
|
|
25
|
+
static #matchesGenerator = (method, selector) => {
|
|
26
|
+
if (selector === undefined || typeof selector === 'string') {
|
|
27
|
+
return token => token instanceof Token && token.matches(selector);
|
|
28
|
+
} else if (selector?.[Symbol.iterator]) {
|
|
29
|
+
return token => new WeakSet(selector).has(token);
|
|
30
|
+
}
|
|
31
|
+
return selector instanceof AstNode
|
|
32
|
+
? token => token === selector
|
|
33
|
+
: typeError(TokenCollection, method, 'String', 'AstNode', 'Iterable');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** @ignore */
|
|
37
|
+
[Symbol.iterator]() {
|
|
38
|
+
return this.array[Symbol.iterator]();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 数组长度 */
|
|
42
|
+
get length() {
|
|
43
|
+
return this.array.length;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 数组长度 */
|
|
47
|
+
size() {
|
|
48
|
+
return this.array.length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 筛选Token节点
|
|
53
|
+
* @returns {Token[]}
|
|
54
|
+
*/
|
|
55
|
+
get #tokens() {
|
|
56
|
+
return this.array.filter(ele => ele instanceof Token);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 第一个Token节点
|
|
61
|
+
* @returns {Token}
|
|
62
|
+
*/
|
|
63
|
+
get #firstToken() {
|
|
64
|
+
return this.array.find(ele => ele instanceof Token);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {Iterable<AstNode>} arr 节点数组
|
|
69
|
+
* @param {TokenCollection} prevObject 前身
|
|
70
|
+
*/
|
|
71
|
+
constructor(arr, prevObject) {
|
|
72
|
+
if (prevObject && !(prevObject instanceof TokenCollection)) {
|
|
73
|
+
this.typeError('constructor', 'TokenCollection');
|
|
74
|
+
}
|
|
75
|
+
for (const token of arr) {
|
|
76
|
+
if (token === undefined) {
|
|
77
|
+
continue;
|
|
78
|
+
} else if (!(token instanceof AstNode)) {
|
|
79
|
+
this.typeError('constructor', 'AstNode');
|
|
80
|
+
} else if (!this.array.includes(token)) {
|
|
81
|
+
this.#roots.add(token.getRootNode());
|
|
82
|
+
this.array.push(token);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this.prevObject = prevObject;
|
|
86
|
+
this.#sort();
|
|
87
|
+
Object.defineProperties(this, {
|
|
88
|
+
array: {writable: false, configurable: false},
|
|
89
|
+
prevObject: {enumerable: prevObject, writable: false, configurable: false},
|
|
90
|
+
});
|
|
91
|
+
Object.freeze(this.array);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 抛出TypeError
|
|
96
|
+
* @param {string} method
|
|
97
|
+
* @param {...string} types 可接受的参数类型
|
|
98
|
+
*/
|
|
99
|
+
typeError(method, ...types) {
|
|
100
|
+
return typeError(this.constructor, method, ...types);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 节点排序 */
|
|
104
|
+
#sort() {
|
|
105
|
+
const rootArray = [...this.#roots];
|
|
106
|
+
this.array.sort(/** @type {(a: AstNode, b: AstNode) => boolean} */ (a, b) => {
|
|
107
|
+
const aRoot = a.getRootNode(),
|
|
108
|
+
bRoot = b.getRootNode();
|
|
109
|
+
return aRoot === bRoot ? a.compareDocumentPosition(b) : rootArray.indexOf(aRoot) - rootArray.indexOf(bRoot);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 转换为数组 */
|
|
114
|
+
toArray() {
|
|
115
|
+
return [...this];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 提取第n个元素
|
|
120
|
+
* @template {unknown} T
|
|
121
|
+
* @param {T} n 序号
|
|
122
|
+
* @returns {T extends number ? AstNode : AstNode[]}
|
|
123
|
+
*/
|
|
124
|
+
get(n) {
|
|
125
|
+
if (Number.isInteger(n)) {
|
|
126
|
+
return this.array.at(n);
|
|
127
|
+
}
|
|
128
|
+
return n === undefined ? this.toArray() : this.typeError('get', 'Number');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 循环
|
|
133
|
+
* @param {CollectionCallback<void, AstNode>} callback
|
|
134
|
+
*/
|
|
135
|
+
each(callback) {
|
|
136
|
+
for (let i = 0; i < this.length; i++) {
|
|
137
|
+
const ele = this.array[i];
|
|
138
|
+
callback.call(ele, i, ele);
|
|
139
|
+
}
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* map方法
|
|
145
|
+
* @param {CollectionCallback<*, AstNode>} callback
|
|
146
|
+
*/
|
|
147
|
+
map(callback) {
|
|
148
|
+
const arr = this.array.map((ele, i) => callback.call(ele, i, ele));
|
|
149
|
+
try {
|
|
150
|
+
return new TokenCollection(arr, this);
|
|
151
|
+
} catch {
|
|
152
|
+
return arr;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 子序列
|
|
158
|
+
* @param {number} start 起点
|
|
159
|
+
* @param {number} end 终点
|
|
160
|
+
*/
|
|
161
|
+
slice(start, end) {
|
|
162
|
+
return new TokenCollection(this.array.slice(start, end), this);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** 第一个元素 */
|
|
166
|
+
first() {
|
|
167
|
+
return this.slice(0, 1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** 最后一个元素 */
|
|
171
|
+
last() {
|
|
172
|
+
return this.slice(-1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 任一元素
|
|
177
|
+
* @param {number} i 序号
|
|
178
|
+
*/
|
|
179
|
+
eq(i) {
|
|
180
|
+
return this.slice(i, i === -1 ? undefined : i + 1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 使用空字符串join */
|
|
184
|
+
toString() {
|
|
185
|
+
return this.array.map(String).join('');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 输出有效部分或设置文本
|
|
190
|
+
* @template {unknown} T
|
|
191
|
+
* @param {T} str 新文本
|
|
192
|
+
* @returns {T extends string|CollectionCallback<string, string> ? this : string}
|
|
193
|
+
*/
|
|
194
|
+
text(str) {
|
|
195
|
+
const /** @type {CollectionCallback<string, string> */ callback = typeof str === 'function' ? str : () => str;
|
|
196
|
+
if (typeof str === 'string' || typeof str === 'function') {
|
|
197
|
+
for (let i = 0; i < this.length; i++) {
|
|
198
|
+
const ele = this.array[i];
|
|
199
|
+
if (ele instanceof Token) {
|
|
200
|
+
try {
|
|
201
|
+
ele.replaceChildren(callback.call(ele, i, ele.text()));
|
|
202
|
+
} catch {}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
return str === undefined ? text(this.toArray()) : this.typeError('text', 'String', 'Function');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 判断是否存在元素满足选择器
|
|
212
|
+
* @param {string|AstNode|Iterable<AstNode>|CollectionCallback<boolean, AstNode>} selector
|
|
213
|
+
*/
|
|
214
|
+
is(selector) {
|
|
215
|
+
return typeof selector === 'function'
|
|
216
|
+
? this.array.some((ele, i) => selector.call(ele, i, ele))
|
|
217
|
+
: this.array.some(TokenCollection.#matchesGenerator('is', selector));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 筛选满足选择器的元素
|
|
222
|
+
* @param {string|AstNode|Iterable<AstNode>|CollectionCallback<boolean, AstNode>} selector
|
|
223
|
+
*/
|
|
224
|
+
filter(selector) {
|
|
225
|
+
return new TokenCollection(this.array.filter(
|
|
226
|
+
(ele, i) => typeof selector === 'function'
|
|
227
|
+
? selector.call(ele, i, ele)
|
|
228
|
+
: TokenCollection.#matchesGenerator('filter', selector)(ele),
|
|
229
|
+
), this);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 筛选不满足选择器的元素
|
|
234
|
+
* @param {string|AstNode|Iterable<AstNode>|CollectionCallback<boolean, AstNode>} selector
|
|
235
|
+
*/
|
|
236
|
+
not(selector) {
|
|
237
|
+
let /** @type {(ele: AstNode, i: number) => boolean} */ callback;
|
|
238
|
+
if (typeof selector === 'function') {
|
|
239
|
+
callback = /** @implements */ (ele, i) => !selector.call(ele, i, ele);
|
|
240
|
+
} else {
|
|
241
|
+
const matches = TokenCollection.#matchesGenerator('not', selector);
|
|
242
|
+
callback = /** @implements */ ele => !matches(ele);
|
|
243
|
+
}
|
|
244
|
+
return new TokenCollection(this.array.filter(callback), this);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 搜索满足选择器的子节点
|
|
249
|
+
* @param {string|AstNode|Iterable<AstNode>} selector
|
|
250
|
+
*/
|
|
251
|
+
find(selector) {
|
|
252
|
+
let arr;
|
|
253
|
+
if (selector === undefined || typeof selector === 'string') {
|
|
254
|
+
arr = this.array.flatMap(token => token instanceof Token ? token.querySelectorAll(selector) : []);
|
|
255
|
+
} else if (selector?.[Symbol.iterator]) {
|
|
256
|
+
arr = [...selector].filter(ele => this.array.some(token => token.contains(ele)));
|
|
257
|
+
} else if (selector instanceof AstNode) {
|
|
258
|
+
arr = this.array.some(token => token.contains(selector)) ? [selector] : [];
|
|
259
|
+
} else {
|
|
260
|
+
this.typeError('find', 'String', 'AstNode', 'Iterable');
|
|
261
|
+
}
|
|
262
|
+
return new TokenCollection(arr, this);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 是否存在满足条件的后代节点
|
|
267
|
+
* @param {string|AstNode} selector
|
|
268
|
+
*/
|
|
269
|
+
has(selector) {
|
|
270
|
+
if (selector === undefined || typeof selector === 'string') {
|
|
271
|
+
return this.array.some(ele => ele instanceof Token && ele.querySelector(selector));
|
|
272
|
+
}
|
|
273
|
+
return selector instanceof AstNode
|
|
274
|
+
? this.array.some(ele => ele.contains(selector))
|
|
275
|
+
: this.typeError('has', 'String', 'AstNode');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 最近的祖先
|
|
280
|
+
* @param {string} selector
|
|
281
|
+
*/
|
|
282
|
+
closest(selector) {
|
|
283
|
+
if (selector === undefined) {
|
|
284
|
+
return new TokenCollection(this.array.map(({parentNode}) => parentNode), this);
|
|
285
|
+
}
|
|
286
|
+
return typeof selector === 'string'
|
|
287
|
+
? new TokenCollection(this.#tokens.map(ele => ele.closest(selector)), this)
|
|
288
|
+
: this.typeError('closest', 'String');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** 第一个Token节点在父容器中的序号 */
|
|
292
|
+
index() {
|
|
293
|
+
const {array: [firstNode]} = this;
|
|
294
|
+
if (firstNode) {
|
|
295
|
+
const {parentNode} = firstNode;
|
|
296
|
+
return parentNode ? parentNode.childNodes.indexOf(firstNode) : 0;
|
|
297
|
+
}
|
|
298
|
+
return -1;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 添加元素
|
|
303
|
+
* @param {AstNode|Iterable<AstNode>} elements 新增的元素
|
|
304
|
+
*/
|
|
305
|
+
add(elements) {
|
|
306
|
+
return new TokenCollection([...this, ...Symbol.iterator in elements ? elements : [elements]], this);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 添加prevObject
|
|
311
|
+
* @param {string} selector
|
|
312
|
+
*/
|
|
313
|
+
addBack(selector) {
|
|
314
|
+
return this.add(selector ? this.prevObject.filter(selector) : this.prevObject);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 带选择器筛选的map
|
|
319
|
+
* @param {string} method
|
|
320
|
+
* @param {(ele: AstNode) => Token} callback
|
|
321
|
+
* @param {string} selector
|
|
322
|
+
*/
|
|
323
|
+
#map(method, callback, selector) {
|
|
324
|
+
return selector === undefined || typeof selector === 'string'
|
|
325
|
+
? new TokenCollection(this.array.map(callback).filter(ele => ele.matches(selector)), this)
|
|
326
|
+
: this.typeError(method, 'String');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 带选择器筛选的flatMap
|
|
331
|
+
* @param {string} method
|
|
332
|
+
* @param {(ele: AstNode) => Token|Token[]} callback
|
|
333
|
+
* @param {string} selector
|
|
334
|
+
*/
|
|
335
|
+
#flatMap(method, callback, selector) {
|
|
336
|
+
return selector === undefined || typeof selector === 'string'
|
|
337
|
+
? new TokenCollection(this.array.flatMap(callback).filter(ele => ele.matches(selector)), this)
|
|
338
|
+
: this.typeError(method, 'String');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 父元素
|
|
343
|
+
* @param {string} selector
|
|
344
|
+
*/
|
|
345
|
+
parent(selector) {
|
|
346
|
+
return this.#map('parent', ele => ele.parentNode, selector);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 祖先
|
|
351
|
+
* @param {string} selector
|
|
352
|
+
*/
|
|
353
|
+
parents(selector) {
|
|
354
|
+
return this.#flatMap('parents', ele => ele.getAncestors(), selector);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* nextElementSibling
|
|
359
|
+
* @param {string} selector
|
|
360
|
+
*/
|
|
361
|
+
next(selector) {
|
|
362
|
+
return this.#map('next', ele => ele.nextElementSibling, selector);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* previousElementSibling
|
|
367
|
+
* @param {string} selector
|
|
368
|
+
*/
|
|
369
|
+
prev(selector) {
|
|
370
|
+
return this.#map('prev', ele => ele.previousElementSibling, selector);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 筛选兄弟
|
|
375
|
+
* @param {string} method
|
|
376
|
+
* @param {(i: number) => number} start 起点
|
|
377
|
+
* @param {(i: number) => number} count 数量
|
|
378
|
+
* @param {string} selector
|
|
379
|
+
*/
|
|
380
|
+
#siblings(method, start, count, selector) {
|
|
381
|
+
if (selector !== undefined && typeof selector !== 'string') {
|
|
382
|
+
this.typeError(method, 'String');
|
|
383
|
+
}
|
|
384
|
+
return new TokenCollection(this.array.flatMap(ele => {
|
|
385
|
+
const {parentNode} = ele;
|
|
386
|
+
if (!parentNode) {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
const {childNodes} = parentNode,
|
|
390
|
+
i = childNodes.indexOf(ele);
|
|
391
|
+
childNodes.splice(start(i), count(i));
|
|
392
|
+
return childNodes;
|
|
393
|
+
}).filter(ele => ele instanceof Token && ele.matches(selector)), this);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* 所有在后的兄弟
|
|
398
|
+
* @param {string} selector
|
|
399
|
+
*/
|
|
400
|
+
nextAll(selector) {
|
|
401
|
+
return this.#siblings('nextAll', () => 0, i => i + 1, selector);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 所有在前的兄弟
|
|
406
|
+
* @param {string} selector
|
|
407
|
+
*/
|
|
408
|
+
prevAll(selector) {
|
|
409
|
+
return this.#siblings('prevAll', i => i, () => Infinity, selector);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 所有在后的兄弟
|
|
414
|
+
* @param {string} selector
|
|
415
|
+
*/
|
|
416
|
+
siblings(selector) {
|
|
417
|
+
return this.#siblings('siblings', i => i, () => 1, selector);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 直到选择器被满足
|
|
422
|
+
* @param {'parents'|'nextAll'|'prevAll'} method
|
|
423
|
+
* @param {string|Token|Iterable<Token>} selector
|
|
424
|
+
* @param {string} filter 额外的筛选选择器
|
|
425
|
+
*/
|
|
426
|
+
#until(method, selector, filter) {
|
|
427
|
+
const originalMethod = `${method.replace(/All$/u, '')}Until`;
|
|
428
|
+
if (filter !== undefined && typeof filter !== 'string') {
|
|
429
|
+
this.typeError(originalMethod, 'String');
|
|
430
|
+
}
|
|
431
|
+
const matches = TokenCollection.#matchesGenerator(originalMethod, selector);
|
|
432
|
+
return new TokenCollection(this.array.flatMap(ele => {
|
|
433
|
+
const /** @type {{array: Token[]}} */ {array} = $(ele)[method](),
|
|
434
|
+
until = array[method === 'nextAll' ? 'findIndex' : 'findLastIndex'](end => matches(end));
|
|
435
|
+
return method === 'nextAll' ? array.slice(0, until) : array.slice(until + 1);
|
|
436
|
+
}).filter(ele => ele.matches(filter)), this);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* 直到选择器被满足的祖先
|
|
441
|
+
* @param {string|Token|Iterable<Token>} selector
|
|
442
|
+
* @param {string} filter 额外的筛选选择器
|
|
443
|
+
*/
|
|
444
|
+
parentsUntil(selector, filter) {
|
|
445
|
+
return this.#until('parents', selector, filter);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* 直到选择器被满足的后方兄弟
|
|
450
|
+
* @param {string|Token|Iterable<Token>} selector
|
|
451
|
+
* @param {string} filter 额外的筛选选择器
|
|
452
|
+
*/
|
|
453
|
+
nextUntil(selector, filter) {
|
|
454
|
+
return this.#until('nextAll', selector, filter);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* 直到选择器被满足的前方兄弟
|
|
459
|
+
* @param {string|Token|Iterable<Token>} selector
|
|
460
|
+
* @param {string} filter 额外的筛选选择器
|
|
461
|
+
*/
|
|
462
|
+
prevUntil(selector, filter) {
|
|
463
|
+
return this.#until('prevAll', selector, filter);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Token子节点
|
|
468
|
+
* @param {string} selector
|
|
469
|
+
*/
|
|
470
|
+
children(selector) {
|
|
471
|
+
return selector === undefined || typeof selector === 'string'
|
|
472
|
+
? new TokenCollection(
|
|
473
|
+
this.#tokens.flatMap(({children}) => children).filter(ele => ele.matches(selector)),
|
|
474
|
+
this,
|
|
475
|
+
)
|
|
476
|
+
: this.typeError('children', 'String');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** 所有子节点 */
|
|
480
|
+
contents() {
|
|
481
|
+
return new TokenCollection(this.array.flatMap(({childNodes}) => childNodes), this);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* 存取数据
|
|
486
|
+
* @param {string|Record<string, *>} key 键
|
|
487
|
+
* @param {*} value 值
|
|
488
|
+
*/
|
|
489
|
+
data(key, value) {
|
|
490
|
+
if (value !== undefined && typeof key !== 'string') {
|
|
491
|
+
this.typeError('data', 'String');
|
|
492
|
+
} else if (value === undefined && !isPlainObject(key)) {
|
|
493
|
+
const data = cache.get(this.#firstToken);
|
|
494
|
+
return key === undefined ? data : data?.[key];
|
|
495
|
+
}
|
|
496
|
+
for (const token of this.#tokens) {
|
|
497
|
+
if (!cache.has(token)) {
|
|
498
|
+
cache.set(token, {});
|
|
499
|
+
}
|
|
500
|
+
const data = cache.get(token);
|
|
501
|
+
if (typeof key === 'string') {
|
|
502
|
+
data[key] = value;
|
|
503
|
+
} else {
|
|
504
|
+
Object.assign(data, key);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return this;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* 删除数据
|
|
512
|
+
* @param {string|string[]} name 键
|
|
513
|
+
*/
|
|
514
|
+
removeData(name) {
|
|
515
|
+
if (name !== undefined && typeof name !== 'string' && !Array.isArray(name)) {
|
|
516
|
+
this.typeError('removeData', 'String', 'Array');
|
|
517
|
+
}
|
|
518
|
+
name = typeof name === 'string' ? name.split(/\s/u) : name;
|
|
519
|
+
for (const token of this.#tokens) {
|
|
520
|
+
if (!cache.has(token)) {
|
|
521
|
+
continue;
|
|
522
|
+
} else if (name === undefined) {
|
|
523
|
+
cache.delete(token);
|
|
524
|
+
} else {
|
|
525
|
+
const data = cache.get(token);
|
|
526
|
+
for (const key of name) {
|
|
527
|
+
delete data[key];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return this;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 添加事件监听
|
|
536
|
+
* @param {string|Record<string, AstListener>} events 事件名
|
|
537
|
+
* @param {string|AstListener} selector
|
|
538
|
+
* @param {AstListener} handler 事件处理
|
|
539
|
+
* @param {boolean} once 是否一次性
|
|
540
|
+
*/
|
|
541
|
+
#addEventListener(events, selector, handler, once = false) {
|
|
542
|
+
if (typeof events !== 'string' && !isPlainObject(events)) {
|
|
543
|
+
this.typeError(once ? 'once' : 'on', 'String', 'Object');
|
|
544
|
+
} else if (typeof selector === 'function') {
|
|
545
|
+
handler = selector;
|
|
546
|
+
selector = undefined;
|
|
547
|
+
}
|
|
548
|
+
const eventPair = typeof events === 'string'
|
|
549
|
+
? events.split(/\s/u).map(/** @returns {[string, AstListener]} */ event => [event, handler])
|
|
550
|
+
: Object.entries(events);
|
|
551
|
+
for (const token of this.#tokens) {
|
|
552
|
+
if (token.matches(selector)) {
|
|
553
|
+
for (const [event, listener] of eventPair) {
|
|
554
|
+
token.addEventListener(event, listener, {once});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* 添加事件监听
|
|
563
|
+
* @param {string|Record<string, AstListener>} events 事件名
|
|
564
|
+
* @param {string|AstListener} selector
|
|
565
|
+
* @param {AstListener} handler 事件处理
|
|
566
|
+
*/
|
|
567
|
+
on(events, selector, handler) {
|
|
568
|
+
return this.#addEventListener(events, selector, handler);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* 添加一次性事件监听
|
|
573
|
+
* @param {string|Record<string, AstListener>} events 事件名
|
|
574
|
+
* @param {string|AstListener} selector
|
|
575
|
+
* @param {AstListener} handler 事件处理
|
|
576
|
+
*/
|
|
577
|
+
one(events, selector, handler) {
|
|
578
|
+
return this.#addEventListener(events, selector, handler, true);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* 移除事件监听
|
|
583
|
+
* @param {string|Record<string, AstListener|undefined>|undefined} events 事件名
|
|
584
|
+
* @param {string|AstListener} selector
|
|
585
|
+
* @param {AstListener} handler 事件处理
|
|
586
|
+
*/
|
|
587
|
+
off(events, selector, handler) {
|
|
588
|
+
if (events !== undefined && typeof events !== 'string' && !isPlainObject(events)) {
|
|
589
|
+
this.typeError('off', 'String', 'Object');
|
|
590
|
+
} else if (typeof selector === 'function') {
|
|
591
|
+
handler = selector;
|
|
592
|
+
selector = undefined;
|
|
593
|
+
}
|
|
594
|
+
let eventPair;
|
|
595
|
+
if (events) {
|
|
596
|
+
eventPair = typeof events === 'string'
|
|
597
|
+
? events.split(/\s/u).map(/** @returns {[string, AstListener]} */ event => [event, handler])
|
|
598
|
+
: Object.entries(events);
|
|
599
|
+
}
|
|
600
|
+
for (const token of this.#tokens) {
|
|
601
|
+
if (!token.matches(selector)) {
|
|
602
|
+
continue;
|
|
603
|
+
} else if (events === undefined) {
|
|
604
|
+
token.removeAllEventListeners();
|
|
605
|
+
} else {
|
|
606
|
+
for (const [event, listener] of eventPair) {
|
|
607
|
+
if (listener === undefined) {
|
|
608
|
+
token.removeAllEventListeners(event);
|
|
609
|
+
} else if (typeof listener === 'function') {
|
|
610
|
+
token.removeEventListener(event, listener);
|
|
611
|
+
} else {
|
|
612
|
+
this.typeError('off', 'String', 'Function');
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return this;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* 触发事件
|
|
622
|
+
* @param {string|Event} event 事件名
|
|
623
|
+
* @param {*} data 事件数据
|
|
624
|
+
*/
|
|
625
|
+
trigger(event, data) {
|
|
626
|
+
for (const token of this) {
|
|
627
|
+
const e = typeof event === 'string' ? new Event(event, {bubbles: true}) : new Event(event.type, event);
|
|
628
|
+
token.dispatchEvent(e, data);
|
|
629
|
+
}
|
|
630
|
+
return this;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* 伪装触发事件
|
|
635
|
+
* @param {string|Event} event 事件名
|
|
636
|
+
* @param {*} data 事件数据
|
|
637
|
+
*/
|
|
638
|
+
triggerHandler(event, data) {
|
|
639
|
+
const {array: [firstNode]} = this;
|
|
640
|
+
if (!firstNode) {
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
643
|
+
const e = typeof event === 'string' ? new Event(event) : event;
|
|
644
|
+
let result;
|
|
645
|
+
for (const listener of firstNode.listEventListeners(e.type)) {
|
|
646
|
+
result = listener(e, data);
|
|
647
|
+
}
|
|
648
|
+
return result;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* 插入文档
|
|
653
|
+
* @param {'append'|'prepend'|'before'|'after'|'replaceChildren'|'replaceWith'} method
|
|
654
|
+
* @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
|
|
655
|
+
* @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
|
|
656
|
+
*/
|
|
657
|
+
#insert(method, content, ...additional) {
|
|
658
|
+
if (typeof content === 'function') {
|
|
659
|
+
for (let i = 0; i < this.length; i++) {
|
|
660
|
+
const token = this.array[i];
|
|
661
|
+
if (token instanceof Token) {
|
|
662
|
+
const result = content.call(token, i, token.toString());
|
|
663
|
+
if (typeof result === 'string' || result instanceof Token) {
|
|
664
|
+
token[method](result);
|
|
665
|
+
} else if (result?.[Symbol.iterator]) {
|
|
666
|
+
token[method](...result);
|
|
667
|
+
} else {
|
|
668
|
+
this.typeError(method, 'String', 'Token');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
} else {
|
|
673
|
+
for (const token of this) {
|
|
674
|
+
if (token instanceof Token) {
|
|
675
|
+
token[method](
|
|
676
|
+
...Symbol.iterator in content ? content : [content],
|
|
677
|
+
...additional.flatMap(ele => Symbol.iterator in ele ? [...ele] : ele),
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return this;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* 在末尾插入
|
|
687
|
+
* @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
|
|
688
|
+
* @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
|
|
689
|
+
*/
|
|
690
|
+
append(content, ...additional) {
|
|
691
|
+
return this.#insert('append', content, ...additional);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* 在开头插入
|
|
696
|
+
* @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
|
|
697
|
+
* @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
|
|
698
|
+
*/
|
|
699
|
+
prepend(content, ...additional) {
|
|
700
|
+
return this.#insert('prepend', content, ...additional);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* 在后方插入
|
|
705
|
+
* @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
|
|
706
|
+
* @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
|
|
707
|
+
*/
|
|
708
|
+
before(content, ...additional) {
|
|
709
|
+
return this.#insert('before', content, ...additional);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* 在前方插入
|
|
714
|
+
* @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
|
|
715
|
+
* @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
|
|
716
|
+
*/
|
|
717
|
+
after(content, ...additional) {
|
|
718
|
+
return this.#insert('after', content, ...additional);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* 替换所有子节点
|
|
723
|
+
* @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
|
|
724
|
+
* @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
|
|
725
|
+
*/
|
|
726
|
+
html(content) {
|
|
727
|
+
if (content === undefined) {
|
|
728
|
+
return this.toString();
|
|
729
|
+
}
|
|
730
|
+
return this.#insert('replaceChildren', content);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* 替换自身
|
|
735
|
+
* @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
|
|
736
|
+
* @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
|
|
737
|
+
*/
|
|
738
|
+
replaceWith(content) {
|
|
739
|
+
return this.#insert('replaceWith', content);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* 移除自身
|
|
744
|
+
* @param {string} selector
|
|
745
|
+
*/
|
|
746
|
+
remove(selector) {
|
|
747
|
+
this.removeData();
|
|
748
|
+
for (const token of this) {
|
|
749
|
+
if (token instanceof Token && token.matches(selector)) {
|
|
750
|
+
token.remove();
|
|
751
|
+
token.removeAllEventListeners();
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return this;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* 移除自身
|
|
759
|
+
* @param {string} selector
|
|
760
|
+
*/
|
|
761
|
+
detach(selector) {
|
|
762
|
+
for (const token of this) {
|
|
763
|
+
if (token instanceof Token && token.matches(selector)) {
|
|
764
|
+
token.remove();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return this;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** 清空子节点 */
|
|
771
|
+
empty() {
|
|
772
|
+
for (const token of this.#tokens) {
|
|
773
|
+
token.replaceChildren();
|
|
774
|
+
}
|
|
775
|
+
return this;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* 深拷贝
|
|
780
|
+
* @param {boolean} withData 是否复制数据
|
|
781
|
+
*/
|
|
782
|
+
clone(withData) {
|
|
783
|
+
return new TokenCollection(this.#tokens.map(ele => {
|
|
784
|
+
const cloned = ele.cloneNode();
|
|
785
|
+
if (withData && cache.has(ele)) {
|
|
786
|
+
cache.set(cloned, structuredClone(cache.get(ele)));
|
|
787
|
+
}
|
|
788
|
+
return cloned;
|
|
789
|
+
}), this);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* 插入到
|
|
794
|
+
* @param {string} method
|
|
795
|
+
* @param {'append'|'prepend'|'before'|'after'|'replaceWith'} elementMethod 对应的AstElement方法
|
|
796
|
+
* @param {Token|Iterable<Token>} target 目标位置
|
|
797
|
+
*/
|
|
798
|
+
#insertAdjacent(method, elementMethod, target) {
|
|
799
|
+
if (target instanceof Token) {
|
|
800
|
+
target[elementMethod](...this);
|
|
801
|
+
} else if (target?.[Symbol.iterator]) {
|
|
802
|
+
for (const token of target) {
|
|
803
|
+
if (token instanceof Token) {
|
|
804
|
+
token[elementMethod](...this);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
this.typeError(method, 'Token', 'Iterable');
|
|
809
|
+
}
|
|
810
|
+
return this;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* 插入到末尾
|
|
815
|
+
* @param {Token|Iterable<Token>} target 目标位置
|
|
816
|
+
*/
|
|
817
|
+
appendTo(target) {
|
|
818
|
+
return this.#insertAdjacent('appendTo', 'append', target);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* 插入到开头
|
|
823
|
+
* @param {Token|Iterable<Token>} target 目标位置
|
|
824
|
+
*/
|
|
825
|
+
prependTo(target) {
|
|
826
|
+
return this.#insertAdjacent('prependTo', 'prepend', target);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* 插入到前方
|
|
831
|
+
* @param {Token|Iterable<Token>} target 目标位置
|
|
832
|
+
*/
|
|
833
|
+
insertBefore(target) {
|
|
834
|
+
return this.#insertAdjacent('insertBefore', 'before', target);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* 插入到后方
|
|
839
|
+
* @param {Token|Iterable<Token>} target 目标位置
|
|
840
|
+
*/
|
|
841
|
+
insertAfter(target) {
|
|
842
|
+
return this.#insertAdjacent('insertAfter', 'after', target);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* 替换全部
|
|
847
|
+
* @param {Token|Iterable<Token>} target 目标位置
|
|
848
|
+
*/
|
|
849
|
+
replaceAll(target) {
|
|
850
|
+
return this.#insertAdjacent('replaceAll', 'replaceWith', target);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* 获取几何属性
|
|
855
|
+
* @param {string|string[]} key 属性键
|
|
856
|
+
*/
|
|
857
|
+
css(key) {
|
|
858
|
+
const /** @type {Record<string, number>} */ style = this.#firstToken?.style;
|
|
859
|
+
if (typeof key === 'string') {
|
|
860
|
+
return style?.[key];
|
|
861
|
+
}
|
|
862
|
+
return Array.isArray(key)
|
|
863
|
+
? Object.fromEntries(key.map(k => [k, style?.[k]]))
|
|
864
|
+
: this.typeError('css', 'String', 'Array');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* 查询或修改值
|
|
869
|
+
* @param {string|boolean|(string|boolean)[]|CollectionCallback<string, string|boolean>} value 值
|
|
870
|
+
*/
|
|
871
|
+
val(value) {
|
|
872
|
+
if (value === undefined) {
|
|
873
|
+
const /** @type {{getValue: () => string|booleand}} */ firstToken = this.#firstToken;
|
|
874
|
+
return firstToken?.getValue && firstToken.getValue();
|
|
875
|
+
}
|
|
876
|
+
let /** @type {(i: number, token: {getValue: () => string|boolean}) => string|boolean} */ getValue;
|
|
877
|
+
if (typeof value === 'string' || typeof value === 'boolean') {
|
|
878
|
+
getValue = /** @implements */ () => value;
|
|
879
|
+
} else if (typeof value === 'function') {
|
|
880
|
+
getValue = /** @implements */ (i, token) => value.call(token, i, token.getValue && token.getValue());
|
|
881
|
+
} else if (Array.isArray(value)) {
|
|
882
|
+
getValue = /** @implements */ i => value[i];
|
|
883
|
+
} else {
|
|
884
|
+
this.typeError('val', 'String', 'Array', 'Function');
|
|
885
|
+
}
|
|
886
|
+
for (let i = 0; i < this.length; i++) {
|
|
887
|
+
const /** @type {{setValue: (value: string|boolean) => void}} */ token = this.array[i];
|
|
888
|
+
if (token instanceof Token && typeof token.setValue === 'function' && token.setValue.length === 1) {
|
|
889
|
+
token.setValue(getValue(i, token));
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return this;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* 查询或修改属性
|
|
897
|
+
* @param {'getAttr'|'getAttribute'} getter 属性getter
|
|
898
|
+
* @param {'setAttr'|'setAttribute'} setter 属性setter
|
|
899
|
+
* @param {string|Record<string, string|boolean>} name 属性名
|
|
900
|
+
* @param {string|boolean|CollectionCallback<string|boolean, string|boolean>} value 属性值
|
|
901
|
+
*/
|
|
902
|
+
#attr(getter, setter, name, value) {
|
|
903
|
+
if (typeof name === 'string' && value === undefined) {
|
|
904
|
+
const firstToken = this.#firstToken;
|
|
905
|
+
return firstToken?.[getter] && firstToken[getter](name);
|
|
906
|
+
}
|
|
907
|
+
for (let i = 0; i < this.length; i++) {
|
|
908
|
+
const token = this.array[i];
|
|
909
|
+
if (token instanceof Token && token[setter]) {
|
|
910
|
+
if (typeof value === 'function') {
|
|
911
|
+
token[setter](name, value.call(token, i, token[getter] && token[getter](name)));
|
|
912
|
+
} else if (isPlainObject(name)) {
|
|
913
|
+
for (const [k, v] of Object.entries(name)) {
|
|
914
|
+
token[setter](k, v);
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
token[setter](name, value);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return this;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* 标签属性
|
|
926
|
+
* @param {string|Record<string, string|boolean>} name 属性名
|
|
927
|
+
* @param {string|boolean|CollectionCallback<string|boolean, string|boolean>} value 属性值
|
|
928
|
+
*/
|
|
929
|
+
attr(name, value) {
|
|
930
|
+
return this.#attr('getAttr', 'setAttr', name, value);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* 节点属性
|
|
935
|
+
* @param {string|Record<string, string|boolean>} name 属性名
|
|
936
|
+
* @param {string|boolean|CollectionCallback<string|boolean, string|boolean>} value 属性值
|
|
937
|
+
*/
|
|
938
|
+
prop(name, value) {
|
|
939
|
+
return this.#attr('getAttribute', 'setAttribute', name, value);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* 移除属性
|
|
944
|
+
* @param {'removeAttr'|'removeAttribute'} method
|
|
945
|
+
* @param {string} name 属性名
|
|
946
|
+
*/
|
|
947
|
+
#removeAttr(method, name) {
|
|
948
|
+
for (const token of this) {
|
|
949
|
+
if (token instanceof Token && token[method]) {
|
|
950
|
+
token[method](name);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return this;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* 标签属性
|
|
958
|
+
* @param {string} name 属性名
|
|
959
|
+
*/
|
|
960
|
+
removeAttr(name) {
|
|
961
|
+
return this.#removeAttr('removeAttr', name);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* 节点属性
|
|
966
|
+
* @param {string} name 属性名
|
|
967
|
+
*/
|
|
968
|
+
removeProp(name) {
|
|
969
|
+
return this.#removeAttr('removeAttribute', name);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* 添加class
|
|
974
|
+
* @this {TokenCollection & {array: AttributesToken[]}}
|
|
975
|
+
* @param {string|string[]|CollectionCallback<string|string[], string>} className 类名
|
|
976
|
+
*/
|
|
977
|
+
addClass(className) {
|
|
978
|
+
/** @type {CollectionCallback<string|string[], string>} */
|
|
979
|
+
const callback = typeof className === 'function' ? className : () => className;
|
|
980
|
+
for (let i = 0; i < this.length; i++) {
|
|
981
|
+
const token = this.array[i],
|
|
982
|
+
{classList} = token;
|
|
983
|
+
if (classList) {
|
|
984
|
+
const newClasses = callback.call(token, i, token.className);
|
|
985
|
+
for (const newClass of Array.isArray(newClasses) ? newClasses : [newClasses]) {
|
|
986
|
+
classList.add(newClass);
|
|
987
|
+
}
|
|
988
|
+
token.className = [...classList].join(' ');
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return this;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* 移除class
|
|
996
|
+
* @this {TokenCollection & {array: AttributesToken[]}}
|
|
997
|
+
* @param {undefined|string|string[]|CollectionCallback<undefined|string|string[], string>} className 类名
|
|
998
|
+
*/
|
|
999
|
+
removeClass(className) {
|
|
1000
|
+
/** @type {CollectionCallback<undefined|string|string[], string>} */
|
|
1001
|
+
const callback = typeof className === 'function' ? className : () => className;
|
|
1002
|
+
for (let i = 0; i < this.length; i++) {
|
|
1003
|
+
const token = this.array[i],
|
|
1004
|
+
{classList} = token;
|
|
1005
|
+
if (classList) {
|
|
1006
|
+
const newClasses = callback.call(token, i, token.className);
|
|
1007
|
+
if (newClasses === undefined) {
|
|
1008
|
+
classList.clear();
|
|
1009
|
+
} else {
|
|
1010
|
+
for (const newClass of Array.isArray(newClasses) ? newClasses : [newClasses]) {
|
|
1011
|
+
classList.delete(newClass);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
token.className = [...classList].join(' ');
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return this;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* 增减class
|
|
1022
|
+
* @this {TokenCollection & {array: AttributesToken[]}}
|
|
1023
|
+
* @param {string|string[]|CollectionCallback<string|string[], string>} className 类名
|
|
1024
|
+
* @param {boolean} state 是否增删
|
|
1025
|
+
*/
|
|
1026
|
+
toggleClass(className, state) {
|
|
1027
|
+
if (typeof state === 'boolean') {
|
|
1028
|
+
return this[state ? 'addClass' : 'removeClass'](className);
|
|
1029
|
+
}
|
|
1030
|
+
/** @type {CollectionCallback<string|string[], string>} */
|
|
1031
|
+
const callback = typeof className === 'function' ? className : () => className;
|
|
1032
|
+
for (let i = 0; i < this.length; i++) {
|
|
1033
|
+
const token = this.array[i],
|
|
1034
|
+
{classList} = token;
|
|
1035
|
+
if (classList) {
|
|
1036
|
+
const newClasses = callback.call(token, i, token.className);
|
|
1037
|
+
for (const newClass of Array.isArray(newClasses) ? new Set(newClasses) : [newClasses]) {
|
|
1038
|
+
classList[classList.has(newClass) ? 'delete' : 'add'](newClass);
|
|
1039
|
+
}
|
|
1040
|
+
token.className = [...classList].join(' ');
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return this;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* 是否带有某class
|
|
1048
|
+
* @this {{array: AttributesToken[]}}
|
|
1049
|
+
* @param {string} className 类名
|
|
1050
|
+
*/
|
|
1051
|
+
hasClass(className) {
|
|
1052
|
+
return this.array.some(token => token.classList?.has(className));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* 全包围
|
|
1057
|
+
* @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
|
|
1058
|
+
* @throws `Error` 不是连续的兄弟节点
|
|
1059
|
+
*/
|
|
1060
|
+
wrapAll(wrapper) {
|
|
1061
|
+
if (typeof wrapper !== 'function' && !Array.isArray(wrapper)) {
|
|
1062
|
+
this.typeError('wrapAll', 'Array', 'Function');
|
|
1063
|
+
}
|
|
1064
|
+
const {array} = this,
|
|
1065
|
+
[firstNode] = array,
|
|
1066
|
+
/** @type {Token} */ ancestor = firstNode?.parentNode,
|
|
1067
|
+
error = new Error('wrapAll 的主体应为普通Token的连续子节点!');
|
|
1068
|
+
if (ancestor?.constructor !== Token) {
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1071
|
+
const {childNodes} = ancestor,
|
|
1072
|
+
i = childNodes.indexOf(firstNode),
|
|
1073
|
+
j = childNodes.findIndex(node => node.contains(array.at(-1)));
|
|
1074
|
+
if (j === -1 || childNodes.slice(i, j + 1).some(node => !array.includes(node))) {
|
|
1075
|
+
throw error;
|
|
1076
|
+
}
|
|
1077
|
+
const [pre, post] = typeof wrapper === 'function' ? wrapper.call(firstNode) : wrapper,
|
|
1078
|
+
config = ancestor.getAttribute('config'),
|
|
1079
|
+
include = ancestor.getAttribute('include'),
|
|
1080
|
+
token = new Token(`${pre}${String(childNodes.slice(i, j + 1))}${post}`, config).parse(undefined, include);
|
|
1081
|
+
this.detach();
|
|
1082
|
+
(childNodes[i - 1]?.after ?? ancestor.prepend)(...token.childNodes);
|
|
1083
|
+
return this;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* 局部包裹
|
|
1088
|
+
* @param {'html'|'replaceWith'} method
|
|
1089
|
+
* @param {string} originalMethod 原方法
|
|
1090
|
+
* @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
|
|
1091
|
+
* @returns {this}
|
|
1092
|
+
*/
|
|
1093
|
+
#wrap(method, originalMethod, wrapper) {
|
|
1094
|
+
if (typeof wrapper !== 'function' && !Array.isArray(wrapper)) {
|
|
1095
|
+
this.typeError(originalMethod, 'Array', 'Function');
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* @implements {CollectionCallback<AstNode|Iterable<AstNode>, string>}
|
|
1100
|
+
* @this {Token}
|
|
1101
|
+
* @param {number} i 序号
|
|
1102
|
+
* @param {string} string 原文本
|
|
1103
|
+
*/
|
|
1104
|
+
const callback = function(i, string) {
|
|
1105
|
+
const [pre, post] = typeof wrapper === 'function' ? wrapper.call(this, i) : wrapper,
|
|
1106
|
+
config = this.getAttribute('config'),
|
|
1107
|
+
include = this.getAttribute('include');
|
|
1108
|
+
return new Token(`${pre}${string}${post}`, config).parse(undefined, include).childNodes;
|
|
1109
|
+
};
|
|
1110
|
+
return this[method](callback);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* 包裹内部
|
|
1115
|
+
* @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
|
|
1116
|
+
*/
|
|
1117
|
+
wrapInner(wrapper) {
|
|
1118
|
+
return this.#wrap('html', 'wrapInner', wrapper);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* 包裹自身
|
|
1123
|
+
* @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
|
|
1124
|
+
*/
|
|
1125
|
+
wrap(wrapper) {
|
|
1126
|
+
return this.#wrap('replaceWith', 'wrap', wrapper);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/** 相对于文档的位置 */
|
|
1130
|
+
offset() {
|
|
1131
|
+
const offset = this.#firstToken?.getBoundingClientRect();
|
|
1132
|
+
return offset && {top: offset.top, left: offset.left};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/** 相对于父容器的位置 */
|
|
1136
|
+
position() {
|
|
1137
|
+
const style = this.#firstToken?.style;
|
|
1138
|
+
return style && {top: style.top, left: style.left};
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/** 高度 */
|
|
1142
|
+
height() {
|
|
1143
|
+
return this.#firstToken?.offsetHeight;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/** 宽度 */
|
|
1147
|
+
width() {
|
|
1148
|
+
return this.#firstToken?.offsetWidth;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/** 内部高度 */
|
|
1152
|
+
innerHeight() {
|
|
1153
|
+
return this.#firstToken?.clientHeight;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/** 内部宽度 */
|
|
1157
|
+
innerWidth() {
|
|
1158
|
+
return this.#firstToken?.clientWidth;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* 代替TokenCollection构造器
|
|
1164
|
+
* @param {AstNode|Iterable<AstNode>} arr 节点数组
|
|
1165
|
+
*/
|
|
1166
|
+
const $ = arr => new TokenCollection(arr instanceof AstNode ? [arr] : arr);
|
|
1167
|
+
/* eslint-disable func-names */
|
|
1168
|
+
$.hasData = /** @param {Token} element */ function hasData(element) {
|
|
1169
|
+
return element instanceof Token ? cache.has(element) : typeError(this, 'hasData', 'Token');
|
|
1170
|
+
};
|
|
1171
|
+
$.data = /** @type {function(Token, string, *): *} */ function data(element, key, value) {
|
|
1172
|
+
if (!(element instanceof Token)) {
|
|
1173
|
+
typeError(this, 'data', 'Token');
|
|
1174
|
+
} else if (key === undefined) {
|
|
1175
|
+
return cache.get(element);
|
|
1176
|
+
} else if (typeof key !== 'string') {
|
|
1177
|
+
typeError(this, 'data', 'String');
|
|
1178
|
+
} else if (value === undefined) {
|
|
1179
|
+
return cache.get(element)?.[key];
|
|
1180
|
+
} else if (!cache.has(element)) {
|
|
1181
|
+
cache.set(element, {});
|
|
1182
|
+
}
|
|
1183
|
+
cache.get(element)[key] = value;
|
|
1184
|
+
return value;
|
|
1185
|
+
};
|
|
1186
|
+
$.removeData = /** @type {function(Token, string): void} */ function removeData(element, name) {
|
|
1187
|
+
if (!(element instanceof Token)) {
|
|
1188
|
+
typeError(this, 'removeData', 'Token');
|
|
1189
|
+
} else if (name === undefined) {
|
|
1190
|
+
cache.delete(element);
|
|
1191
|
+
} else if (typeof name !== 'string') {
|
|
1192
|
+
typeError(this, 'removeData', 'String');
|
|
1193
|
+
} else if (cache.has(element)) {
|
|
1194
|
+
const data = cache.get(element);
|
|
1195
|
+
delete data[name];
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
/* eslint-enable func-names */
|
|
1199
|
+
Object.defineProperty($, 'cache', {value: cache, enumerable: false, writable: false, configurable: false});
|
|
1200
|
+
|
|
1201
|
+
Parser.tool.$ = __filename;
|
|
1202
|
+
module.exports = $;
|