wikiparser-node 0.3.1 → 0.5.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 +1 -1
- package/config/default.json +13 -17
- package/config/llwiki.json +11 -79
- package/config/moegirl.json +7 -1
- package/config/zhwiki.json +1269 -0
- package/index.js +130 -97
- package/lib/element.js +410 -518
- package/lib/node.js +493 -115
- package/lib/ranges.js +27 -19
- package/lib/text.js +175 -0
- package/lib/title.js +14 -6
- package/mixin/attributeParent.js +70 -24
- package/mixin/fixedToken.js +18 -10
- package/mixin/hidden.js +6 -4
- package/mixin/sol.js +39 -12
- package/package.json +17 -4
- package/parser/brackets.js +18 -18
- package/parser/commentAndExt.js +16 -14
- package/parser/converter.js +14 -13
- package/parser/externalLinks.js +12 -11
- package/parser/hrAndDoubleUnderscore.js +24 -14
- package/parser/html.js +8 -7
- package/parser/links.js +13 -13
- package/parser/list.js +12 -11
- package/parser/magicLinks.js +11 -10
- package/parser/quotes.js +6 -5
- package/parser/selector.js +175 -0
- package/parser/table.js +31 -24
- package/src/arg.js +91 -43
- package/src/atom/hidden.js +5 -2
- package/src/atom/index.js +17 -9
- package/src/attribute.js +210 -101
- package/src/converter.js +78 -43
- package/src/converterFlags.js +104 -45
- package/src/converterRule.js +136 -78
- package/src/extLink.js +81 -27
- package/src/gallery.js +63 -20
- package/src/heading.js +58 -20
- package/src/html.js +138 -48
- package/src/imageParameter.js +93 -58
- package/src/index.js +314 -186
- package/src/link/category.js +22 -54
- package/src/link/file.js +83 -32
- package/src/link/galleryImage.js +21 -7
- package/src/link/index.js +170 -81
- package/src/magicLink.js +64 -14
- package/src/nowiki/comment.js +36 -10
- package/src/nowiki/dd.js +37 -22
- package/src/nowiki/doubleUnderscore.js +21 -7
- package/src/nowiki/hr.js +11 -7
- package/src/nowiki/index.js +16 -9
- package/src/nowiki/list.js +2 -2
- package/src/nowiki/noinclude.js +8 -4
- package/src/nowiki/quote.js +38 -7
- package/src/onlyinclude.js +24 -7
- package/src/parameter.js +102 -62
- package/src/syntax.js +23 -20
- package/src/table/index.js +282 -174
- package/src/table/td.js +112 -61
- package/src/table/tr.js +135 -74
- package/src/tagPair/ext.js +30 -23
- package/src/tagPair/include.js +26 -11
- package/src/tagPair/index.js +72 -29
- package/src/transclude.js +235 -127
- package/tool/index.js +42 -32
- package/util/debug.js +21 -18
- package/util/diff.js +76 -0
- package/util/lint.js +40 -0
- package/util/string.js +56 -26
- package/.eslintrc.json +0 -319
- package/errors/README +0 -1
- package/jsconfig.json +0 -7
- package/printed/README +0 -1
- package/typings/element.d.ts +0 -28
- package/typings/index.d.ts +0 -52
- package/typings/node.d.ts +0 -23
- package/typings/parser.d.ts +0 -9
- package/typings/table.d.ts +0 -14
- package/typings/token.d.ts +0 -22
- package/typings/tool.d.ts +0 -10
package/src/converterRule.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const {undo} = require('../util/debug'),
|
|
4
4
|
{noWrap} = require('../util/string'),
|
|
5
|
-
|
|
5
|
+
Parser = require('..'),
|
|
6
6
|
Token = require('.'),
|
|
7
7
|
AtomToken = require('./atom');
|
|
8
8
|
|
|
@@ -12,138 +12,192 @@ const {undo} = require('../util/debug'),
|
|
|
12
12
|
*/
|
|
13
13
|
class ConverterRuleToken extends Token {
|
|
14
14
|
type = 'converter-rule';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
/** 语言变体 */
|
|
17
|
+
get variant() {
|
|
18
|
+
return this.childNodes.at(-2)?.text()?.trim() ?? '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set variant(variant) {
|
|
22
|
+
this.setVariant(variant);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 是否是单向转换 */
|
|
26
|
+
get unidirectional() {
|
|
27
|
+
return this.childNodes.length === 3;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set unidirectional(unidirectional) {
|
|
31
|
+
if (unidirectional === false) {
|
|
32
|
+
this.makeBidirectional();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 是否是双向转换 */
|
|
37
|
+
get bidirectional() {
|
|
38
|
+
return this.childNodes.length === 2;
|
|
39
|
+
}
|
|
18
40
|
|
|
19
41
|
/**
|
|
20
|
-
* @param {string} rule
|
|
42
|
+
* @param {string} rule 转换规则
|
|
43
|
+
* @param {boolean} hasColon 是否带有":"
|
|
21
44
|
* @param {accum} accum
|
|
22
45
|
*/
|
|
23
46
|
constructor(rule, hasColon = true, config = Parser.getConfig(), accum = []) {
|
|
24
47
|
super(undefined, config, true, accum, {AtomToken: ':'});
|
|
25
|
-
if (
|
|
26
|
-
super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
|
|
27
|
-
} else {
|
|
48
|
+
if (hasColon) {
|
|
28
49
|
const i = rule.indexOf(':'),
|
|
29
50
|
j = rule.slice(0, i).indexOf('=>'),
|
|
30
|
-
v =
|
|
51
|
+
v = j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i),
|
|
31
52
|
{variants} = config;
|
|
32
|
-
if (
|
|
33
|
-
super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
|
|
34
|
-
} else {
|
|
53
|
+
if (variants.includes(v.trim())) {
|
|
35
54
|
super.insertAt(new AtomToken(v, 'converter-rule-variant', config, accum));
|
|
36
55
|
super.insertAt(new AtomToken(rule.slice(i + 1), 'converter-rule-to', config, accum));
|
|
37
|
-
if (j
|
|
38
|
-
this.bidirectional = true;
|
|
39
|
-
} else {
|
|
56
|
+
if (j !== -1) {
|
|
40
57
|
super.insertAt(new AtomToken(rule.slice(0, j), 'converter-rule-from', config, accum), 0);
|
|
41
|
-
this.unidirectional = true;
|
|
42
58
|
}
|
|
59
|
+
} else {
|
|
60
|
+
super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
|
|
43
61
|
}
|
|
62
|
+
} else {
|
|
63
|
+
super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
|
|
44
64
|
}
|
|
45
|
-
this.
|
|
65
|
+
this.getAttribute('protectChildren')('1:');
|
|
46
66
|
}
|
|
47
67
|
|
|
68
|
+
/** @override */
|
|
48
69
|
cloneNode() {
|
|
49
|
-
const cloned = this.
|
|
70
|
+
const cloned = this.cloneChildNodes(),
|
|
50
71
|
placeholders = ['', 'zh:', '=>zh:'],
|
|
51
72
|
placeholder = placeholders[cloned.length - 1],
|
|
52
73
|
token = Parser.run(() => new ConverterRuleToken(placeholder, placeholder, this.getAttribute('config')));
|
|
53
74
|
for (let i = 0; i < cloned.length; i++) {
|
|
54
|
-
token.
|
|
75
|
+
token.childNodes[i].safeReplaceWith(cloned[i]);
|
|
55
76
|
}
|
|
56
77
|
token.afterBuild();
|
|
57
78
|
return token;
|
|
58
79
|
}
|
|
59
80
|
|
|
81
|
+
/** @override */
|
|
60
82
|
afterBuild() {
|
|
61
|
-
|
|
62
|
-
this
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{variants} = that.getAttribute('config');
|
|
71
|
-
if (variants.includes(v)) {
|
|
72
|
-
that.setAttribute('variant', v);
|
|
73
|
-
} else {
|
|
74
|
-
undo(e, data);
|
|
75
|
-
throw new Error(`无效的语言变体:${v}`);
|
|
76
|
-
}
|
|
83
|
+
const /** @type {AstListener} */ converterRuleListener = (e, data) => {
|
|
84
|
+
const {childNodes} = this,
|
|
85
|
+
{prevTarget} = e;
|
|
86
|
+
if (childNodes.length > 1 && childNodes.at(-2) === prevTarget) {
|
|
87
|
+
const v = prevTarget.text().trim(),
|
|
88
|
+
{variants} = this.getAttribute('config');
|
|
89
|
+
if (!variants.includes(v)) {
|
|
90
|
+
undo(e, data);
|
|
91
|
+
throw new Error(`无效的语言变体:${v}`);
|
|
77
92
|
}
|
|
78
|
-
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
79
95
|
this.addEventListener(['remove', 'insert', 'text', 'replace'], converterRuleListener);
|
|
80
96
|
return this;
|
|
81
97
|
}
|
|
82
98
|
|
|
83
99
|
/**
|
|
84
|
-
* @
|
|
100
|
+
* @override
|
|
101
|
+
* @param {number} i 移除位置
|
|
85
102
|
* @returns {AtomToken}
|
|
103
|
+
* @throws `Error` 至少保留1个子节点
|
|
86
104
|
*/
|
|
87
105
|
removeAt(i) {
|
|
88
|
-
if (
|
|
89
|
-
throw new
|
|
106
|
+
if (this.childNodes.length === 1) {
|
|
107
|
+
throw new Error(`${this.constructor.name} 需至少保留 1 个子节点!`);
|
|
90
108
|
}
|
|
91
|
-
|
|
109
|
+
const removed = super.removeAt(i);
|
|
110
|
+
if (this.childNodes.length === 1) {
|
|
111
|
+
this.firstChild.type = 'converter-rule-noconvert';
|
|
112
|
+
}
|
|
113
|
+
return removed;
|
|
92
114
|
}
|
|
93
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @override
|
|
118
|
+
* @throws `Error` 请勿手动插入子节点
|
|
119
|
+
*/
|
|
94
120
|
insertAt() {
|
|
95
|
-
throw new Error(
|
|
121
|
+
throw new Error(`转换规则语法复杂,请勿尝试对 ${this.constructor.name} 手动插入子节点!`);
|
|
96
122
|
}
|
|
97
123
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
124
|
+
/**
|
|
125
|
+
* @override
|
|
126
|
+
* @param {string} selector
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
toString(selector) {
|
|
130
|
+
if (this.childNodes.length === 3 && !(selector && this.matches(selector))) {
|
|
131
|
+
const {childNodes: [from, variant, to]} = this;
|
|
132
|
+
return `${from.toString(selector)}=>${variant.toString(selector)}:${to.toString(selector)}`;
|
|
103
133
|
}
|
|
104
|
-
return super.toString(':');
|
|
134
|
+
return super.toString(selector, ':');
|
|
105
135
|
}
|
|
106
136
|
|
|
107
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* @override
|
|
139
|
+
* @param {number} i 子节点序号
|
|
140
|
+
*/
|
|
108
141
|
getGaps(i = 0) {
|
|
109
|
-
const {length} = this
|
|
142
|
+
const {childNodes: {length}} = this;
|
|
110
143
|
i = i < 0 ? i + length : i;
|
|
111
144
|
return i === 0 && length === 3 ? 2 : 1;
|
|
112
145
|
}
|
|
113
146
|
|
|
114
|
-
/** @
|
|
147
|
+
/** @override */
|
|
148
|
+
print() {
|
|
149
|
+
if (this.childNodes.length === 3) {
|
|
150
|
+
const {childNodes: [from, variant, to]} = this;
|
|
151
|
+
return `<span class="wpb-converter-rule">${from.print()}=>${variant.print()}:${to.print()}</span>`;
|
|
152
|
+
}
|
|
153
|
+
return super.print({sep: ':'});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @override
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
115
160
|
text() {
|
|
116
161
|
if (this.childNodes.length === 3) {
|
|
117
|
-
const [from, variant, to] = this
|
|
162
|
+
const {childNodes: [from, variant, to]} = this;
|
|
118
163
|
return `${from.text()}=>${variant.text()}:${to.text()}`;
|
|
119
164
|
}
|
|
120
165
|
return super.text(':');
|
|
121
166
|
}
|
|
122
167
|
|
|
168
|
+
/** 修改为不转换 */
|
|
123
169
|
noConvert() {
|
|
124
|
-
|
|
125
|
-
|
|
170
|
+
const {childNodes: {length}, lastChild} = this;
|
|
171
|
+
for (let i = 0; i < length - 1; i++) { // ConverterRuleToken只能从前往后删除子节点
|
|
172
|
+
this.removeAt(0);
|
|
126
173
|
}
|
|
127
|
-
|
|
174
|
+
lastChild.type = 'converter-rule-noconvert';
|
|
128
175
|
}
|
|
129
176
|
|
|
130
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* 设置转换目标
|
|
179
|
+
* @param {string} to 转换目标
|
|
180
|
+
* @throws `SyntaxError` 非法的转换目标
|
|
181
|
+
*/
|
|
131
182
|
setTo(to) {
|
|
132
183
|
to = String(to);
|
|
133
184
|
const config = this.getAttribute('config'),
|
|
134
185
|
root = Parser.parse(`-{|${config.variants[0]}:${to}}-`, this.getAttribute('include'), undefined, config),
|
|
135
|
-
{childNodes: {length},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
) {
|
|
186
|
+
{childNodes: {length}, firstChild: converter} = root,
|
|
187
|
+
{lastChild: converterRule, type, childNodes: {length: converterLength}} = converter;
|
|
188
|
+
if (length !== 1 || type !== 'converter' || converterLength !== 2 || converterRule.childNodes.length !== 2) {
|
|
139
189
|
throw new SyntaxError(`非法的转换目标:${noWrap(to)}`);
|
|
140
190
|
}
|
|
141
|
-
const {lastChild} =
|
|
142
|
-
|
|
143
|
-
this.
|
|
191
|
+
const {lastChild} = converterRule;
|
|
192
|
+
converterRule.destroy(true);
|
|
193
|
+
this.lastChild.safeReplaceWith(lastChild);
|
|
144
194
|
}
|
|
145
195
|
|
|
146
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* 设置语言变体
|
|
198
|
+
* @param {string} variant 语言变体
|
|
199
|
+
* @throws `RangeError` 无效的语言变体
|
|
200
|
+
*/
|
|
147
201
|
setVariant(variant) {
|
|
148
202
|
if (typeof variant !== 'string') {
|
|
149
203
|
this.typeError('setVariant', 'String');
|
|
@@ -154,45 +208,49 @@ class ConverterRuleToken extends Token {
|
|
|
154
208
|
throw new RangeError(`无效的语言变体:${v}`);
|
|
155
209
|
} else if (this.childNodes.length === 1) {
|
|
156
210
|
super.insertAt(Parser.run(() => new AtomToken(variant, 'converter-rule-variant', config)), 0);
|
|
157
|
-
this.setAttribute('bidirectional', true);
|
|
158
211
|
} else {
|
|
159
|
-
this.
|
|
212
|
+
this.childNodes.at(-2).setText(variant);
|
|
160
213
|
}
|
|
161
|
-
this.setAttribute('variant', v);
|
|
162
214
|
}
|
|
163
215
|
|
|
164
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* 设置转换原文
|
|
218
|
+
* @param {string} from 转换原文
|
|
219
|
+
* @throws `Error` 尚未指定语言变体
|
|
220
|
+
* @throws `SyntaxError` 非法的转换原文
|
|
221
|
+
*/
|
|
165
222
|
setFrom(from) {
|
|
166
|
-
const {variant} = this;
|
|
223
|
+
const {variant, unidirectional} = this;
|
|
167
224
|
if (!variant) {
|
|
168
225
|
throw new Error('请先指定语言变体!');
|
|
169
226
|
}
|
|
170
227
|
from = String(from);
|
|
171
228
|
const config = this.getAttribute('config'),
|
|
172
229
|
root = Parser.parse(`-{|${from}=>${variant}:}-`, this.getAttribute('include'), undefined, config),
|
|
173
|
-
{childNodes: {length},
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
) {
|
|
230
|
+
{childNodes: {length}, firstChild: converter} = root,
|
|
231
|
+
{type, childNodes: {length: converterLength}, lastChild: converterRule} = converter;
|
|
232
|
+
if (length !== 1 || type !== 'converter' || converterLength !== 2 || converterRule.childNodes.length !== 3) {
|
|
177
233
|
throw new SyntaxError(`非法的转换原文:${noWrap(from)}`);
|
|
178
234
|
}
|
|
179
|
-
if (
|
|
180
|
-
this.
|
|
235
|
+
if (unidirectional) {
|
|
236
|
+
this.firstChild.safeReplaceWith(converterRule.firstChild);
|
|
181
237
|
} else {
|
|
182
|
-
super.insertAt(
|
|
183
|
-
this.setAttribute('unidirectional', true).setAttribute('bidirectional', false);
|
|
238
|
+
super.insertAt(converterRule.firstChild, 0);
|
|
184
239
|
}
|
|
185
240
|
}
|
|
186
241
|
|
|
187
|
-
/**
|
|
242
|
+
/**
|
|
243
|
+
* 修改为单向转换
|
|
244
|
+
* @param {string} from 转换来源
|
|
245
|
+
*/
|
|
188
246
|
makeUnidirectional(from) {
|
|
189
247
|
this.setFrom(from);
|
|
190
248
|
}
|
|
191
249
|
|
|
250
|
+
/** 修改为双向转换 */
|
|
192
251
|
makeBidirectional() {
|
|
193
252
|
if (this.unidirectional) {
|
|
194
253
|
super.removeAt(0);
|
|
195
|
-
this.setAttribute('unidirectional', false).setAttribute('bidirectional', true);
|
|
196
254
|
}
|
|
197
255
|
}
|
|
198
256
|
}
|
package/src/extLink.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {noWrap, normalizeSpace} = require('../util/string'),
|
|
4
|
-
|
|
4
|
+
Parser = require('..'),
|
|
5
5
|
Token = require('.'),
|
|
6
6
|
MagicLinkToken = require('./magicLink');
|
|
7
7
|
|
|
@@ -13,19 +13,42 @@ class ExtLinkToken extends Token {
|
|
|
13
13
|
type = 'ext-link';
|
|
14
14
|
#space;
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* 协议
|
|
18
|
+
* @this {{firstChild: MagicLinkToken}}
|
|
19
|
+
*/
|
|
17
20
|
get protocol() {
|
|
18
21
|
return this.firstChild.protocol;
|
|
19
22
|
}
|
|
23
|
+
|
|
20
24
|
/** @this {{firstChild: MagicLinkToken}} */
|
|
21
25
|
set protocol(value) {
|
|
22
26
|
this.firstChild.protocol = value;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
|
-
*
|
|
27
|
-
* @
|
|
28
|
-
|
|
30
|
+
* 和内链保持一致
|
|
31
|
+
* @this {{firstChild: MagicLinkToken}}
|
|
32
|
+
*/
|
|
33
|
+
get link() {
|
|
34
|
+
return this.firstChild.link;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set link(url) {
|
|
38
|
+
this.setTarget(url);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 链接显示文字 */
|
|
42
|
+
get innerText() {
|
|
43
|
+
return this.childNodes.length > 1
|
|
44
|
+
? this.lastChild.text()
|
|
45
|
+
: `[${this.getRootNode().querySelectorAll('ext-link[childElementCount=1]').indexOf(this) + 1}]`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} url 网址
|
|
50
|
+
* @param {string} space 空白字符
|
|
51
|
+
* @param {string} text 链接文字
|
|
29
52
|
* @param {accum} accum
|
|
30
53
|
*/
|
|
31
54
|
constructor(url, space, text, config = Parser.getConfig(), accum = []) {
|
|
@@ -37,79 +60,110 @@ class ExtLinkToken extends Token {
|
|
|
37
60
|
inner.type = 'ext-link-text';
|
|
38
61
|
this.appendChild(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
|
|
39
62
|
}
|
|
40
|
-
this.protectChildren(0);
|
|
63
|
+
this.getAttribute('protectChildren')(0);
|
|
41
64
|
}
|
|
42
65
|
|
|
66
|
+
/** @override */
|
|
43
67
|
cloneNode() {
|
|
44
|
-
const [url, text] = this.
|
|
68
|
+
const [url, text] = this.cloneChildNodes(),
|
|
45
69
|
token = Parser.run(() => new ExtLinkToken(undefined, '', '', this.getAttribute('config')));
|
|
46
|
-
token.
|
|
70
|
+
token.firstChild.safeReplaceWith(url);
|
|
47
71
|
if (text) {
|
|
48
72
|
token.appendChild(text);
|
|
49
73
|
}
|
|
50
74
|
return token;
|
|
51
75
|
}
|
|
52
76
|
|
|
77
|
+
/** 修正空白字符 */
|
|
53
78
|
#correct() {
|
|
54
79
|
if (!this.#space && this.childNodes.length > 1
|
|
55
80
|
// 都替换成`<`肯定不对,但无妨
|
|
56
|
-
&& /^[^[\]<>"{\0-\
|
|
81
|
+
&& /^[^[\]<>"{\0-\x1F\x7F\p{Zs}\uFFFD]/u.test(this.lastChild.text().replace(/&[lg]t;/u, '<'))
|
|
57
82
|
) {
|
|
58
83
|
this.#space = ' ';
|
|
59
84
|
}
|
|
60
85
|
}
|
|
61
86
|
|
|
62
|
-
|
|
87
|
+
/**
|
|
88
|
+
* @override
|
|
89
|
+
* @param {string} selector
|
|
90
|
+
*/
|
|
91
|
+
toString(selector) {
|
|
92
|
+
if (selector && this.matches(selector)) {
|
|
93
|
+
return '';
|
|
94
|
+
} else if (this.childNodes.length === 1) {
|
|
95
|
+
return `[${super.toString(selector)}${this.#space}]`;
|
|
96
|
+
}
|
|
63
97
|
this.#correct();
|
|
64
|
-
|
|
98
|
+
normalizeSpace(this.lastChild);
|
|
99
|
+
return `[${super.toString(selector, this.#space)}]`;
|
|
65
100
|
}
|
|
66
101
|
|
|
102
|
+
/** @override */
|
|
67
103
|
getPadding() {
|
|
68
|
-
this.#correct();
|
|
69
104
|
return 1;
|
|
70
105
|
}
|
|
71
106
|
|
|
107
|
+
/** @override */
|
|
72
108
|
getGaps() {
|
|
73
109
|
this.#correct();
|
|
74
110
|
return this.#space.length;
|
|
75
111
|
}
|
|
76
112
|
|
|
113
|
+
/** @override */
|
|
114
|
+
print() {
|
|
115
|
+
const {childNodes: {length}} = this;
|
|
116
|
+
return super.print(length > 1 ? {pre: '[', sep: this.#space, post: ']'} : {pre: '[', post: `${this.#space}]`});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @override */
|
|
77
120
|
text() {
|
|
78
|
-
|
|
121
|
+
normalizeSpace(this.childNodes[1]);
|
|
122
|
+
return `[${super.text(' ')}]`;
|
|
79
123
|
}
|
|
80
124
|
|
|
81
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* 获取网址
|
|
127
|
+
* @this {{firstChild: MagicLinkToken}}
|
|
128
|
+
*/
|
|
82
129
|
getUrl() {
|
|
83
|
-
return this.
|
|
130
|
+
return this.firstChild.getUrl();
|
|
84
131
|
}
|
|
85
132
|
|
|
86
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* 设置链接目标
|
|
135
|
+
* @param {string|URL} url 网址
|
|
136
|
+
* @throws `SyntaxError` 非法的外链目标
|
|
137
|
+
*/
|
|
87
138
|
setTarget(url) {
|
|
88
139
|
url = String(url);
|
|
89
140
|
const root = Parser.parse(`[${url}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
|
|
90
|
-
{childNodes: {length},
|
|
91
|
-
if (length !== 1 ||
|
|
141
|
+
{childNodes: {length}, firstChild: extLink} = root;
|
|
142
|
+
if (length !== 1 || extLink.type !== 'ext-link' || extLink.childNodes.length !== 1) {
|
|
92
143
|
throw new SyntaxError(`非法的外链目标:${url}`);
|
|
93
144
|
}
|
|
94
|
-
const {firstChild} =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this.firstElementChild.safeReplaceWith(firstChild);
|
|
145
|
+
const {firstChild} = extLink;
|
|
146
|
+
extLink.destroy(true);
|
|
147
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
98
148
|
}
|
|
99
149
|
|
|
100
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* 设置链接显示文字
|
|
152
|
+
* @param {string} text 链接显示文字
|
|
153
|
+
* @throws `SyntaxError` 非法的链接显示文字
|
|
154
|
+
*/
|
|
101
155
|
setLinkText(text) {
|
|
102
156
|
text = String(text);
|
|
103
157
|
const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
|
|
104
|
-
{childNodes: {length},
|
|
105
|
-
if (length !== 1 ||
|
|
158
|
+
{childNodes: {length}, firstChild: extLink} = root;
|
|
159
|
+
if (length !== 1 || extLink.type !== 'ext-link' || extLink.childNodes.length !== 2) {
|
|
106
160
|
throw new SyntaxError(`非法的外链文字:${noWrap(text)}`);
|
|
107
161
|
}
|
|
108
|
-
const {lastChild} =
|
|
162
|
+
const {lastChild} = extLink;
|
|
109
163
|
if (this.childNodes.length === 1) {
|
|
110
164
|
this.appendChild(lastChild);
|
|
111
165
|
} else {
|
|
112
|
-
this.
|
|
166
|
+
this.lastChild.safeReplaceWith(lastChild);
|
|
113
167
|
}
|
|
114
168
|
this.#space ||= ' ';
|
|
115
169
|
}
|
package/src/gallery.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
/** @type {Parser} */ Parser = require('..'),
|
|
3
|
+
const Parser = require('..'),
|
|
5
4
|
Token = require('.'),
|
|
6
|
-
GalleryImageToken = require('./link/galleryImage')
|
|
5
|
+
GalleryImageToken = require('./link/galleryImage'),
|
|
6
|
+
HiddenToken = require('./atom/hidden');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* gallery标签
|
|
10
|
-
* @classdesc `{childNodes: (
|
|
10
|
+
* @classdesc `{childNodes: ...(GalleryImageToken|HiddenToken|AstText)}`
|
|
11
11
|
*/
|
|
12
12
|
class GalleryToken extends Token {
|
|
13
13
|
type = 'ext-inner';
|
|
14
14
|
name = 'gallery';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @param {string} inner
|
|
17
|
+
* @param {string} inner 标签内部wikitext
|
|
18
18
|
* @param {accum} accum
|
|
19
19
|
*/
|
|
20
20
|
constructor(inner, config = Parser.getConfig(), accum = []) {
|
|
21
|
-
super(undefined, config, true, accum, {
|
|
21
|
+
super(undefined, config, true, accum, {AstText: ':', GalleryImageToken: ':'});
|
|
22
22
|
for (const line of inner?.split('\n') ?? []) {
|
|
23
|
-
const matches = /^([^|]+)(?:\|(.*))
|
|
23
|
+
const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
|
|
24
24
|
if (!matches) {
|
|
25
|
-
this.appendChild(line);
|
|
25
|
+
this.appendChild(line.trim() ? new HiddenToken(line, undefined, config, [], {AstText: ':'}) : line);
|
|
26
26
|
continue;
|
|
27
27
|
}
|
|
28
28
|
const [, file, alt] = matches;
|
|
@@ -32,34 +32,51 @@ class GalleryToken extends Token {
|
|
|
32
32
|
} catch {
|
|
33
33
|
title = this.normalizeTitle(file, 6, true);
|
|
34
34
|
}
|
|
35
|
-
if (
|
|
36
|
-
this.appendChild(line);
|
|
37
|
-
} else {
|
|
35
|
+
if (title.valid) {
|
|
38
36
|
this.appendChild(new GalleryImageToken(file, alt, title, config, accum));
|
|
37
|
+
} else {
|
|
38
|
+
this.appendChild(new HiddenToken(line, undefined, config, [], {AstText: ':'}));
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/** @override */
|
|
43
44
|
cloneNode() {
|
|
44
|
-
const cloned = this.
|
|
45
|
+
const cloned = this.cloneChildNodes(),
|
|
45
46
|
token = Parser.run(() => new GalleryToken(undefined, this.getAttribute('config')));
|
|
46
47
|
token.append(...cloned);
|
|
47
48
|
return token;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* @override
|
|
53
|
+
* @param {string} selector
|
|
54
|
+
*/
|
|
55
|
+
toString(selector) {
|
|
56
|
+
return super.toString(selector, '\n');
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
/** @override */
|
|
54
60
|
getGaps() {
|
|
55
61
|
return 1;
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
/** @override */
|
|
65
|
+
print() {
|
|
66
|
+
return super.print({sep: '\n'});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @override */
|
|
58
70
|
text() {
|
|
59
|
-
return text(
|
|
71
|
+
return super.text('\n').replaceAll(/\n\s*\n/gu, '\n');
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* 插入图片
|
|
76
|
+
* @param {string} file 图片文件名
|
|
77
|
+
* @param {number} i 插入位置
|
|
78
|
+
* @throws `SyntaxError` 非法的文件名
|
|
79
|
+
*/
|
|
63
80
|
insertImage(file, i = this.childNodes.length) {
|
|
64
81
|
let title;
|
|
65
82
|
try {
|
|
@@ -67,11 +84,37 @@ class GalleryToken extends Token {
|
|
|
67
84
|
} catch {
|
|
68
85
|
title = this.normalizeTitle(file, 6, true);
|
|
69
86
|
}
|
|
70
|
-
if (
|
|
71
|
-
|
|
87
|
+
if (title.valid) {
|
|
88
|
+
const token = Parser.run(() => new GalleryImageToken(file, undefined, title, this.getAttribute('config')));
|
|
89
|
+
return this.insertAt(token, i);
|
|
90
|
+
}
|
|
91
|
+
throw new SyntaxError(`非法的文件名:${file}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @override
|
|
96
|
+
* @param {number} start 起始位置
|
|
97
|
+
*/
|
|
98
|
+
lint(start = 0) {
|
|
99
|
+
const {top, left} = this.getRootNode().posFromIndex(start),
|
|
100
|
+
/** @type {LintError[]} */ errors = [];
|
|
101
|
+
for (let i = 0, cur = start; i < this.childNodes.length; i++) {
|
|
102
|
+
const child = this.childNodes[i],
|
|
103
|
+
str = String(child);
|
|
104
|
+
if (child.type === 'hidden' && str.trim() && !/^<!--.*-->$/u.test(str)) {
|
|
105
|
+
errors.push({
|
|
106
|
+
message: '图库中的无效内容',
|
|
107
|
+
startLine: top + i,
|
|
108
|
+
endLine: top + i,
|
|
109
|
+
startCol: i ? 0 : left,
|
|
110
|
+
endCol: i ? str.length : left + str.length,
|
|
111
|
+
});
|
|
112
|
+
} else if (child.type !== 'hidden' && child.type !== 'text') {
|
|
113
|
+
errors.push(...child.lint(cur));
|
|
114
|
+
}
|
|
115
|
+
cur += str.length + 1;
|
|
72
116
|
}
|
|
73
|
-
|
|
74
|
-
return this.insertAt(token, i);
|
|
117
|
+
return errors;
|
|
75
118
|
}
|
|
76
119
|
}
|
|
77
120
|
|