wikiparser-node 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.eslintrc.json +472 -34
  2. package/README.md +1 -1
  3. package/config/default.json +58 -30
  4. package/config/llwiki.json +22 -90
  5. package/config/moegirl.json +51 -13
  6. package/config/zhwiki.json +1269 -0
  7. package/index.js +114 -104
  8. package/lib/element.js +448 -440
  9. package/lib/node.js +335 -115
  10. package/lib/ranges.js +27 -18
  11. package/lib/text.js +146 -0
  12. package/lib/title.js +13 -5
  13. package/mixin/attributeParent.js +70 -24
  14. package/mixin/fixedToken.js +14 -6
  15. package/mixin/hidden.js +6 -4
  16. package/mixin/sol.js +27 -10
  17. package/package.json +9 -3
  18. package/parser/brackets.js +22 -17
  19. package/parser/commentAndExt.js +18 -16
  20. package/parser/converter.js +14 -13
  21. package/parser/externalLinks.js +12 -11
  22. package/parser/hrAndDoubleUnderscore.js +23 -14
  23. package/parser/html.js +10 -9
  24. package/parser/links.js +15 -14
  25. package/parser/list.js +12 -11
  26. package/parser/magicLinks.js +12 -11
  27. package/parser/quotes.js +6 -5
  28. package/parser/selector.js +175 -0
  29. package/parser/table.js +25 -18
  30. package/printed/example.json +120 -0
  31. package/src/arg.js +56 -32
  32. package/src/atom/hidden.js +5 -2
  33. package/src/atom/index.js +17 -9
  34. package/src/attribute.js +182 -100
  35. package/src/converter.js +68 -41
  36. package/src/converterFlags.js +67 -45
  37. package/src/converterRule.js +117 -65
  38. package/src/extLink.js +66 -18
  39. package/src/gallery.js +42 -15
  40. package/src/heading.js +34 -15
  41. package/src/html.js +97 -35
  42. package/src/imageParameter.js +83 -54
  43. package/src/index.js +299 -178
  44. package/src/link/category.js +20 -52
  45. package/src/link/file.js +59 -28
  46. package/src/link/galleryImage.js +21 -7
  47. package/src/link/index.js +146 -60
  48. package/src/magicLink.js +34 -12
  49. package/src/nowiki/comment.js +22 -10
  50. package/src/nowiki/dd.js +37 -22
  51. package/src/nowiki/doubleUnderscore.js +16 -7
  52. package/src/nowiki/hr.js +11 -7
  53. package/src/nowiki/index.js +16 -9
  54. package/src/nowiki/list.js +2 -2
  55. package/src/nowiki/noinclude.js +8 -4
  56. package/src/nowiki/quote.js +11 -7
  57. package/src/onlyinclude.js +19 -7
  58. package/src/parameter.js +65 -38
  59. package/src/syntax.js +26 -20
  60. package/src/table/index.js +260 -165
  61. package/src/table/td.js +98 -52
  62. package/src/table/tr.js +102 -58
  63. package/src/tagPair/ext.js +27 -19
  64. package/src/tagPair/include.js +16 -11
  65. package/src/tagPair/index.js +64 -29
  66. package/src/transclude.js +170 -93
  67. package/test/api.js +83 -0
  68. package/test/real.js +133 -0
  69. package/test/test.js +28 -0
  70. package/test/util.js +80 -0
  71. package/tool/index.js +41 -31
  72. package/typings/api.d.ts +13 -0
  73. package/typings/array.d.ts +28 -0
  74. package/typings/event.d.ts +24 -0
  75. package/typings/index.d.ts +46 -4
  76. package/typings/node.d.ts +15 -9
  77. package/typings/parser.d.ts +7 -0
  78. package/typings/tool.d.ts +3 -2
  79. package/util/debug.js +21 -18
  80. package/util/string.js +40 -27
  81. package/typings/element.d.ts +0 -28
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..'),
3
+ const Parser = require('..'),
4
4
  Token = require('.'),
5
5
  AtomToken = require('./atom');
6
6
 
@@ -13,7 +13,7 @@ class ConverterFlagsToken extends Token {
13
13
  /** @type {string[]} */ #flags;
14
14
 
15
15
  /**
16
- * @param {string[]} flags
16
+ * @param {string[]} flags 转换类型标记
17
17
  * @param {accum} accum
18
18
  */
19
19
  constructor(flags, config = Parser.getConfig(), accum = []) {
@@ -21,30 +21,34 @@ class ConverterFlagsToken extends Token {
21
21
  this.append(...flags.map(flag => new AtomToken(flag, 'converter-flag', config, accum)));
22
22
  }
23
23
 
24
+ /** @override */
24
25
  cloneNode() {
25
- const cloned = this.cloneChildren(),
26
+ const cloned = this.cloneChildNodes(),
26
27
  token = Parser.run(() => new ConverterFlagsToken([], this.getAttribute('config')));
27
28
  token.append(...cloned);
28
29
  token.afterBuild();
29
30
  return token;
30
31
  }
31
32
 
32
- /** @complexity `n` */
33
+ /**
34
+ * @override
35
+ * @complexity `n`
36
+ */
33
37
  afterBuild() {
34
38
  this.#flags = this.children.map(child => child.text().trim());
35
- const that = this,
36
- /** @type {AstListener} */ converterFlagsListener = ({prevTarget}) => {
37
- if (prevTarget) {
38
- that.#flags[that.childNodes.indexOf(prevTarget)] = prevTarget.text().trim();
39
- }
40
- };
39
+ const /** @type {AstListener} */ converterFlagsListener = ({prevTarget}) => {
40
+ if (prevTarget) {
41
+ this.#flags[this.childNodes.indexOf(prevTarget)] = prevTarget.text().trim();
42
+ }
43
+ };
41
44
  this.addEventListener(['remove', 'insert', 'text', 'replace'], converterFlagsListener);
42
45
  return this;
43
46
  }
44
47
 
45
48
  /**
49
+ * @override
46
50
  * @template {string} T
47
- * @param {T} key
51
+ * @param {T} key 属性键
48
52
  * @returns {TokenAttribute<T>}
49
53
  */
50
54
  getAttribute(key) {
@@ -55,7 +59,8 @@ class ConverterFlagsToken extends Token {
55
59
  }
56
60
 
57
61
  /**
58
- * @param {number} i
62
+ * @override
63
+ * @param {number} i 移除位置
59
64
  * @complexity `n`
60
65
  */
61
66
  removeAt(i) {
@@ -65,7 +70,9 @@ class ConverterFlagsToken extends Token {
65
70
  }
66
71
 
67
72
  /**
68
- * @param {AtomToken} token
73
+ * @override
74
+ * @param {AtomToken} token 待插入的子节点
75
+ * @param {number} i 插入位置
69
76
  * @complexity `n`
70
77
  */
71
78
  insertAt(token, i = this.childNodes.length) {
@@ -74,20 +81,27 @@ class ConverterFlagsToken extends Token {
74
81
  return token;
75
82
  }
76
83
 
77
- toString() {
78
- return super.toString(';');
84
+ /**
85
+ * @override
86
+ * @param {string} selector
87
+ */
88
+ toString(selector) {
89
+ return super.toString(selector, ';');
79
90
  }
80
91
 
92
+ /** @override */
81
93
  getGaps() {
82
94
  return 1;
83
95
  }
84
96
 
97
+ /** @override */
85
98
  text() {
86
99
  return super.text(';');
87
100
  }
88
101
 
89
102
  /**
90
- * @param {string} flag
103
+ * 获取转换类型标记节点
104
+ * @param {string} flag 转换类型标记
91
105
  * @returns {AtomToken[]}
92
106
  * @complexity `n`
93
107
  */
@@ -95,16 +109,28 @@ class ConverterFlagsToken extends Token {
95
109
  return this.#flags.includes(flag) ? this.children.filter(child => child.text().trim() === flag) : [];
96
110
  }
97
111
 
112
+ /** 获取所有转换类型标记 */
98
113
  getAllFlags() {
99
114
  return new Set(this.#flags);
100
115
  }
101
116
 
102
- /** @complexity `n` */
117
+ /**
118
+ * 获取未知转换类型标记
119
+ * @complexity `n`
120
+ */
121
+ getUnknownFlags() {
122
+ return this.#flags.filter(flag => /\{\{[^{}]+\}\}/u.test(flag));
123
+ }
124
+
125
+ /**
126
+ * 获取有效转换类型标记
127
+ * @complexity `n`
128
+ */
103
129
  getEffectiveFlags() {
104
130
  const {variants} = this.getAttribute('config'),
105
131
  variantFlags = this.#flags.filter(flag => variants.includes(flag)),
106
- unknownFlags = this.#flags.filter(flag => /{{.+}}/.test(flag));
107
- if (variantFlags.length) {
132
+ unknownFlags = this.getUnknownFlags();
133
+ if (variantFlags.length > 0) {
108
134
  return new Set([...variantFlags, ...unknownFlags]);
109
135
  }
110
136
  const validFlags = ['A', 'T', 'R', 'D', '-', 'H', 'N'],
@@ -120,10 +146,9 @@ class ConverterFlagsToken extends Token {
120
146
  } else if (flags.has('H')) {
121
147
  const hasT = flags.has('T'),
122
148
  hasD = flags.has('D');
123
- if (hasT && hasD) {
124
- return new Set(['+', 'H', 'T', 'D']);
125
- }
126
- return new Set(['+', 'H', ...hasT ? ['T'] : [], ...hasD ? ['D'] : [], ...unknownFlags]);
149
+ return hasT && hasD
150
+ ? new Set(['+', 'H', 'T', 'D'])
151
+ : new Set(['+', 'H', ...hasT ? ['T'] : [], ...hasD ? ['D'] : [], ...unknownFlags]);
127
152
  }
128
153
  if (flags.size === 1 && flags.has('T')) {
129
154
  flags.add('H');
@@ -138,32 +163,26 @@ class ConverterFlagsToken extends Token {
138
163
  return flags;
139
164
  }
140
165
 
141
- /** @complexity `n` */
142
- getUnknownFlags() {
143
- return [...this.getFlags()].filter(flag => /{{.+}}/.test(flag));
144
- }
145
-
146
- /** @param {string} flag */
166
+ /**
167
+ * 是否具有某转换类型标记
168
+ * @param {string} flag 转换类型标记
169
+ */
147
170
  hasFlag(flag) {
148
- if (typeof flag !== 'string') {
149
- this.typeError('hasFlag', 'String');
150
- }
151
- return this.#flags.includes(flag);
171
+ return typeof flag === 'string' ? this.#flags.includes(flag) : this.typeError('hasFlag', 'String');
152
172
  }
153
173
 
154
174
  /**
155
- * @param {string} flag
175
+ * 是否具有某有效转换类型标记
176
+ * @param {string} flag 转换类型标记
156
177
  * @complexity `n`
157
178
  */
158
179
  hasEffectiveFlag(flag) {
159
- if (typeof flag !== 'string') {
160
- this.typeError('hasFlag', 'String');
161
- }
162
- return this.getEffectiveFlags().has(flag);
180
+ return typeof flag === 'string' ? this.getEffectiveFlags().has(flag) : this.typeError('hasFlag', 'String');
163
181
  }
164
182
 
165
183
  /**
166
- * @param {string} flag
184
+ * 移除某转换类型标记
185
+ * @param {string} flag 转换类型标记
167
186
  * @complexity `n²`
168
187
  */
169
188
  removeFlag(flag) {
@@ -173,7 +192,8 @@ class ConverterFlagsToken extends Token {
173
192
  }
174
193
 
175
194
  /**
176
- * @param {string} flag
195
+ * 添加转换类型标记
196
+ * @param {string} flag 转换类型标记
177
197
  * @complexity `n`
178
198
  */
179
199
  #newFlag(flag) {
@@ -182,7 +202,8 @@ class ConverterFlagsToken extends Token {
182
202
  }
183
203
 
184
204
  /**
185
- * @param {string} flag
205
+ * 设置转换类型标记
206
+ * @param {string} flag 转换类型标记
186
207
  * @complexity `n`
187
208
  */
188
209
  setFlag(flag) {
@@ -194,14 +215,15 @@ class ConverterFlagsToken extends Token {
194
215
  }
195
216
 
196
217
  /**
197
- * @param {string} flag
218
+ * 开关转换类型标记
219
+ * @param {string} flag 转换类型标记
198
220
  * @complexity `n²`
199
221
  */
200
222
  toggleFlag(flag) {
201
- if (!this.#flags.includes(flag)) {
202
- this.#newFlag(flag);
203
- } else {
223
+ if (this.#flags.includes(flag)) {
204
224
  this.removeFlag(flag);
225
+ } else {
226
+ this.#newFlag(flag);
205
227
  }
206
228
  }
207
229
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const {undo} = require('../util/debug'),
4
4
  {noWrap} = require('../util/string'),
5
- /** @type {Parser} */ Parser = require('..'),
5
+ Parser = require('..'),
6
6
  Token = require('.'),
7
7
  AtomToken = require('./atom');
8
8
 
@@ -12,41 +12,62 @@ const {undo} = require('../util/debug'),
12
12
  */
13
13
  class ConverterRuleToken extends Token {
14
14
  type = 'converter-rule';
15
- variant = '';
16
- unidirectional = false;
17
- bidirectional = false;
15
+
16
+ /** 语言变体 */
17
+ get variant() {
18
+ return this.children.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 (!hasColon) {
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 = (j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i)).trim(),
51
+ v = j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i),
31
52
  {variants} = config;
32
- if (!variants.includes(v)) {
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 === -1) {
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.seal(['variant', 'unidirectional', 'bidirectional']);
65
+ this.getAttribute('protectChildren')('1:');
46
66
  }
47
67
 
68
+ /** @override */
48
69
  cloneNode() {
49
- const cloned = this.cloneChildren(),
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')));
@@ -57,77 +78,98 @@ class ConverterRuleToken extends Token {
57
78
  return token;
58
79
  }
59
80
 
81
+ /** @override */
60
82
  afterBuild() {
61
- if (this.childNodes.length > 1) {
62
- this.setAttribute('variant', this.children.at(-2).text().trim());
63
- }
64
- const that = this,
65
- /** @type {AstListener} */ converterRuleListener = (e, data) => {
66
- const {childNodes} = that,
67
- {prevTarget} = e;
68
- if (childNodes.length > 1 && childNodes.at(-2) === prevTarget) {
69
- const v = prevTarget.text().trim(),
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
- * @param {number} i
100
+ * @override
101
+ * @param {number} i 移除位置
85
102
  * @returns {AtomToken}
103
+ * @throws `Error` 至少保留1个子节点
86
104
  */
87
105
  removeAt(i) {
88
- if (i !== 0 && i !== -this.childNodes.length) {
89
- throw new RangeError(`${this.constructor.name} 禁止移除第 ${i} 个子节点!`);
106
+ if (this.childNodes.length === 1) {
107
+ throw new Error(`${this.constructor.name} 需至少保留 1 个子节点!`);
108
+ }
109
+ const removed = super.removeAt(i);
110
+ if (this.childNodes.length === 1) {
111
+ this.firstChild.type = 'converter-rule-noconvert';
90
112
  }
91
- return super.removeAt(i);
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
- /** @returns {string} */
99
- toString() {
100
- if (this.childNodes.length === 3) {
101
- const [from, variant, to] = this.children;
102
- return `${from.toString()}=>${variant.toString()}:${to.toString()}`;
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 {children: [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
- /** @param {number} i */
137
+ /**
138
+ * @override
139
+ * @param {number} i 子节点序号
140
+ */
108
141
  getGaps(i = 0) {
109
- const {length} = this.childNodes;
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
- /** @returns {string} */
147
+ /**
148
+ * @override
149
+ * @returns {string}
150
+ */
115
151
  text() {
116
152
  if (this.childNodes.length === 3) {
117
- const [from, variant, to] = this.children;
153
+ const {children: [from, variant, to]} = this;
118
154
  return `${from.text()}=>${variant.text()}:${to.text()}`;
119
155
  }
120
156
  return super.text(':');
121
157
  }
122
158
 
159
+ /** 修改为不转换 */
123
160
  noConvert() {
124
- for (let i = this.childNodes.length - 2; i >= 0; i--) {
125
- super.removeAt(i);
161
+ const {childNodes: {length}, lastChild} = this;
162
+ for (let i = 0; i < length - 1; i++) { // ConverterRuleToken只能从前往后删除子节点
163
+ this.removeAt(0);
126
164
  }
127
- this.setAttribute('unidirectional', false).setAttribute('bidirectional', false).setAttribute('variant', '');
165
+ lastChild.type = 'converter-rule-noconvert';
128
166
  }
129
167
 
130
- /** @param {string} to */
168
+ /**
169
+ * 设置转换目标
170
+ * @param {string} to 转换目标
171
+ * @throws `SyntaxError` 非法的转换目标
172
+ */
131
173
  setTo(to) {
132
174
  to = String(to);
133
175
  const config = this.getAttribute('config'),
@@ -138,12 +180,17 @@ class ConverterRuleToken extends Token {
138
180
  ) {
139
181
  throw new SyntaxError(`非法的转换目标:${noWrap(to)}`);
140
182
  }
141
- const {lastChild} = firstElementChild.lastElementChild;
142
- firstElementChild.lastElementChild.removeAt(0);
183
+ const {lastElementChild} = firstElementChild,
184
+ {lastChild} = lastElementChild;
185
+ lastElementChild.destroy(true);
143
186
  this.lastElementChild.safeReplaceWith(lastChild);
144
187
  }
145
188
 
146
- /** @param {string} variant */
189
+ /**
190
+ * 设置语言变体
191
+ * @param {string} variant 语言变体
192
+ * @throws `RangeError` 无效的语言变体
193
+ */
147
194
  setVariant(variant) {
148
195
  if (typeof variant !== 'string') {
149
196
  this.typeError('setVariant', 'String');
@@ -154,16 +201,19 @@ class ConverterRuleToken extends Token {
154
201
  throw new RangeError(`无效的语言变体:${v}`);
155
202
  } else if (this.childNodes.length === 1) {
156
203
  super.insertAt(Parser.run(() => new AtomToken(variant, 'converter-rule-variant', config)), 0);
157
- this.setAttribute('bidirectional', true);
158
204
  } else {
159
205
  this.children.at(-2).setText(variant);
160
206
  }
161
- this.setAttribute('variant', v);
162
207
  }
163
208
 
164
- /** @param {string} from */
209
+ /**
210
+ * 设置转换原文
211
+ * @param {string} from 转换原文
212
+ * @throws `Error` 尚未指定语言变体
213
+ * @throws `SyntaxError` 非法的转换原文
214
+ */
165
215
  setFrom(from) {
166
- const {variant} = this;
216
+ const {variant, unidirectional} = this;
167
217
  if (!variant) {
168
218
  throw new Error('请先指定语言变体!');
169
219
  }
@@ -176,23 +226,25 @@ class ConverterRuleToken extends Token {
176
226
  ) {
177
227
  throw new SyntaxError(`非法的转换原文:${noWrap(from)}`);
178
228
  }
179
- if (this.unidirectional) {
229
+ if (unidirectional) {
180
230
  this.firstElementChild.safeReplaceWith(firstElementChild.lastElementChild.firstChild);
181
231
  } else {
182
232
  super.insertAt(firstElementChild.lastElementChild.firstChild, 0);
183
- this.setAttribute('unidirectional', true).setAttribute('bidirectional', false);
184
233
  }
185
234
  }
186
235
 
187
- /** @param {string} from */
236
+ /**
237
+ * 修改为单向转换
238
+ * @param {string} from 转换来源
239
+ */
188
240
  makeUnidirectional(from) {
189
241
  this.setFrom(from);
190
242
  }
191
243
 
244
+ /** 修改为双向转换 */
192
245
  makeBidirectional() {
193
246
  if (this.unidirectional) {
194
247
  super.removeAt(0);
195
- this.setAttribute('unidirectional', false).setAttribute('bidirectional', true);
196
248
  }
197
249
  }
198
250
  }
package/src/extLink.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {noWrap, normalizeSpace} = require('../util/string'),
4
- /** @type {Parser} */ Parser = require('..'),
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
- /** @this {{firstChild: MagicLinkToken}} */
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
- * @param {string} url
27
- * @param {string} space
28
- * @param {string} text
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.lastElementChild.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,11 +60,12 @@ 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.cloneChildren(),
68
+ const [url, text] = this.cloneChildNodes(),
45
69
  token = Parser.run(() => new ExtLinkToken(undefined, '', '', this.getAttribute('config')));
46
70
  token.firstElementChild.safeReplaceWith(url);
47
71
  if (text) {
@@ -50,40 +74,61 @@ class ExtLinkToken extends Token {
50
74
  return token;
51
75
  }
52
76
 
77
+ /** 修正空白字符 */
53
78
  #correct() {
54
79
  if (!this.#space && this.childNodes.length > 1
55
80
  // 都替换成`<`肯定不对,但无妨
56
- && /^[^[\]<>"{\x00-\x20\x7f\p{Zs}\ufffd]/u.test(this.lastElementChild.text().replace(/&[lg]t;/, '<'))
81
+ && /^[^[\]<>"{\0-\x1F\x7F\p{Zs}\uFFFD]/u.test(this.lastElementChild.text().replace(/&[lg]t;/u, '<'))
57
82
  ) {
58
83
  this.#space = ' ';
59
84
  }
60
85
  }
61
86
 
62
- toString() {
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
- return `[${this.firstElementChild.toString()}${this.#space}${normalizeSpace(this.children[1])}]`;
98
+ normalizeSpace(this.lastElementChild);
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 */
77
114
  text() {
78
- return `[${super.text(' ').replaceAll('\n', ' ')}]`;
115
+ normalizeSpace(this.children[1]);
116
+ return `[${super.text(' ')}]`;
79
117
  }
80
118
 
81
- /** @this {ExtLinkToken & {firstElementChild: MagicLinkToken}} */
119
+ /**
120
+ * 获取网址
121
+ * @this {{firstChild: MagicLinkToken}}
122
+ */
82
123
  getUrl() {
83
- return this.firstElementChild.getUrl();
124
+ return this.firstChild.getUrl();
84
125
  }
85
126
 
86
- /** @param {string|URL} url */
127
+ /**
128
+ * 设置链接目标
129
+ * @param {string|URL} url 网址
130
+ * @throws `SyntaxError` 非法的外链目标
131
+ */
87
132
  setTarget(url) {
88
133
  url = String(url);
89
134
  const root = Parser.parse(`[${url}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
@@ -92,12 +137,15 @@ class ExtLinkToken extends Token {
92
137
  throw new SyntaxError(`非法的外链目标:${url}`);
93
138
  }
94
139
  const {firstChild} = firstElementChild;
95
- root.destroy();
96
- firstElementChild.destroy();
140
+ firstElementChild.destroy(true);
97
141
  this.firstElementChild.safeReplaceWith(firstChild);
98
142
  }
99
143
 
100
- /** @param {string} text */
144
+ /**
145
+ * 设置链接显示文字
146
+ * @param {string} text 链接显示文字
147
+ * @throws `SyntaxError` 非法的链接显示文字
148
+ */
101
149
  setLinkText(text) {
102
150
  text = String(text);
103
151
  const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),