wikiplus-highlight 2.60.3 → 3.0.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 +18 -47
- package/dist/main.js +85 -0
- package/package.json +25 -15
- package/styles.css +0 -138
- package/dist/main.min.js +0 -8
- package/dist/main.min.js.map +0 -1
- package/fold.js +0 -264
- package/i18n/en.json +0 -35
- package/i18n/ka.json +0 -35
- package/i18n/zh-hans.json +0 -35
- package/i18n/zh-hant.json +0 -35
- package/lint.js +0 -200
- package/main.js +0 -1006
- package/matchtags.js +0 -324
- package/search.js +0 -258
package/matchtags.js
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
2
|
-
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
|
3
|
-
// Modified for MediaWiki by Bhsd <https://github.com/bhsd-harry>
|
|
4
|
-
|
|
5
|
-
(() => {
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
const {Pos, cmpPos, Init} = CodeMirror,
|
|
9
|
-
tagStart = /<(\/?)([a-z]\w*)(?=[\s/>]|$)/giu,
|
|
10
|
-
voidTags = new Set(['br', 'wbr', 'hr', 'img']),
|
|
11
|
-
maxScanLines = 1000;
|
|
12
|
-
|
|
13
|
-
/** @ignore */
|
|
14
|
-
class Iter {
|
|
15
|
-
/**
|
|
16
|
-
* @param {CodeMirror.Editor} cm CodeMirror实例
|
|
17
|
-
* @param {CodeMirror.Position} pos 当前位置
|
|
18
|
-
*/
|
|
19
|
-
constructor(cm, {line, ch}) {
|
|
20
|
-
this.line = line;
|
|
21
|
-
this.ch = ch;
|
|
22
|
-
this.cm = cm;
|
|
23
|
-
this.text = cm.getLine(line);
|
|
24
|
-
this.min = Math.max(line - maxScanLines + 1, cm.firstLine());
|
|
25
|
-
this.max = Math.min(line + maxScanLines - 1, cm.lastLine());
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** 提取当前位置 */
|
|
29
|
-
get pos() {
|
|
30
|
-
return Pos(this.line, this.ch);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** 是否是标签 */
|
|
34
|
-
isTag() {
|
|
35
|
-
const type = this.cm.getTokenTypeAt(this.pos);
|
|
36
|
-
return /\b(?:mw-(?:html|ext)tag|tag\b)/u.test(type);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 判断是否是`<`或`>`
|
|
41
|
-
* @param {number} ch 列号
|
|
42
|
-
*/
|
|
43
|
-
bracketAt(ch) {
|
|
44
|
-
const type = this.cm.getTokenTypeAt(Pos(this.line, ch + 1));
|
|
45
|
-
return /\b(?:mw-(?:html|ext)tag-)?bracket\b/u.test(type);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Jump to the start of the next line */
|
|
49
|
-
nextLine() {
|
|
50
|
-
if (this.line >= this.max) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
this.ch = 0;
|
|
54
|
-
this.text = this.cm.getLine(++this.line);
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Jump to the end of the previous line */
|
|
59
|
-
prevLine() {
|
|
60
|
-
if (this.line <= this.min) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
this.text = this.cm.getLine(--this.line);
|
|
64
|
-
this.ch = this.text.length;
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Jump to the letter after a `>` towards the line end */
|
|
69
|
-
toTagEnd() {
|
|
70
|
-
for (;;) {
|
|
71
|
-
const gt = this.text.indexOf('>', this.ch);
|
|
72
|
-
if (gt === -1) {
|
|
73
|
-
if (this.nextLine()) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
this.ch = gt + 1;
|
|
79
|
-
if (this.bracketAt(gt)) {
|
|
80
|
-
return this.text[gt - 1] === '/' ? 'selfClose' : 'regular';
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Jump to a `<` towards the line start */
|
|
86
|
-
toTagStart() {
|
|
87
|
-
for (;;) {
|
|
88
|
-
const lt = this.ch > 0 ? this.text.lastIndexOf('<', this.ch - 1) : -1;
|
|
89
|
-
if (lt === -1) {
|
|
90
|
-
if (this.prevLine()) {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
} else if (!this.bracketAt(lt)) {
|
|
95
|
-
this.ch = lt;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
tagStart.lastIndex = lt;
|
|
99
|
-
this.ch = lt;
|
|
100
|
-
const match = tagStart.exec(this.text);
|
|
101
|
-
if (match && match.index === lt) {
|
|
102
|
-
return match;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Jump to the start of the last line, or the letter after a `tagStart` */
|
|
108
|
-
toNextTag() {
|
|
109
|
-
for (;;) {
|
|
110
|
-
tagStart.lastIndex = this.ch;
|
|
111
|
-
const found = tagStart.exec(this.text);
|
|
112
|
-
if (!found) {
|
|
113
|
-
if (this.nextLine()) {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
this.ch = found.index + found[0].length;
|
|
119
|
-
if (this.bracketAt(found.index)) {
|
|
120
|
-
return found;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Jump to the end of the first line, or a non-bracket `>`, or the letter after a tag bracket `>` */
|
|
126
|
-
toPrevTag() {
|
|
127
|
-
for (;;) {
|
|
128
|
-
const gt = this.ch > 0 ? this.text.lastIndexOf('>', this.ch - 1) : -1;
|
|
129
|
-
if (gt === -1) {
|
|
130
|
-
if (this.prevLine()) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
return undefined;
|
|
134
|
-
} else if (this.bracketAt(gt)) {
|
|
135
|
-
this.ch = gt + 1;
|
|
136
|
-
return this.text[gt - 1] === '/' ? 'selfClose' : 'regular';
|
|
137
|
-
}
|
|
138
|
-
this.ch = gt;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// eslint-disable-next-line jsdoc/require-returns-check
|
|
143
|
-
/**
|
|
144
|
-
* 搜索匹配的闭合标签
|
|
145
|
-
* @param {string} tag 标签名
|
|
146
|
-
* @returns {CodeMirror.MatchingTag}
|
|
147
|
-
*/
|
|
148
|
-
findMatchingClose(tag) {
|
|
149
|
-
const /** @type {string[]} */ stack = [];
|
|
150
|
-
for (;;) {
|
|
151
|
-
const next = this.toNextTag();
|
|
152
|
-
if (!next) {
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
const from = Pos(this.line, this.ch - next[0].length),
|
|
156
|
-
end = this.toTagEnd(),
|
|
157
|
-
tagName = next[2].toLowerCase();
|
|
158
|
-
if (!end) {
|
|
159
|
-
return undefined;
|
|
160
|
-
} else if (end === 'selfClose' || voidTags.has(tagName)) {
|
|
161
|
-
continue;
|
|
162
|
-
} else if (next[1]) { // closing tag
|
|
163
|
-
let i = stack.length - 1;
|
|
164
|
-
for (; i >= 0; --i) {
|
|
165
|
-
if (stack[i] === tagName) {
|
|
166
|
-
stack.length = i;
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (i < 0 && (!tag || tag === tagName)) {
|
|
171
|
-
return {tag: tagName, from, to: this.pos};
|
|
172
|
-
}
|
|
173
|
-
} else { // opening tag
|
|
174
|
-
stack.push(tagName);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// eslint-disable-next-line jsdoc/require-returns-check
|
|
180
|
-
/**
|
|
181
|
-
* 搜索匹配的开启标签
|
|
182
|
-
* @param {string|undefined} tag 标签名
|
|
183
|
-
* @returns {CodeMirror.MatchingTag}
|
|
184
|
-
*/
|
|
185
|
-
findMatchingOpen(tag) {
|
|
186
|
-
const /** @type {string[]} */ stack = [];
|
|
187
|
-
for (;;) {
|
|
188
|
-
const prev = this.toPrevTag();
|
|
189
|
-
if (!prev) {
|
|
190
|
-
return undefined;
|
|
191
|
-
}
|
|
192
|
-
const {pos: to} = this,
|
|
193
|
-
start = this.toTagStart();
|
|
194
|
-
if (!start) {
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
const tagName = start[2].toLowerCase();
|
|
198
|
-
if (prev === 'selfClose' || voidTags.has(tagName)) {
|
|
199
|
-
continue;
|
|
200
|
-
} else if (start[1]) { // closing tag
|
|
201
|
-
stack.push(tagName);
|
|
202
|
-
} else { // opening tag
|
|
203
|
-
let i = stack.length - 1;
|
|
204
|
-
for (; i >= 0; --i) {
|
|
205
|
-
if (stack[i] === tagName) {
|
|
206
|
-
stack.length = i;
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (i < 0 && (!tag || tag === tagName)) {
|
|
211
|
-
return {tag: tagName, from: this.pos, to};
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
CodeMirror.defineExtension(
|
|
219
|
-
'findMatchingTag',
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* @this {CodeMirror.Editor}
|
|
223
|
-
* @param {CodeMirror.Position} pos 当前位置
|
|
224
|
-
* @returns {CodeMirror.MatchingTagPair}
|
|
225
|
-
*/
|
|
226
|
-
function(pos) {
|
|
227
|
-
const iter = new Iter(this, pos);
|
|
228
|
-
if (!iter.isTag()) {
|
|
229
|
-
return undefined;
|
|
230
|
-
}
|
|
231
|
-
const end = iter.toTagEnd(),
|
|
232
|
-
to = end && iter.pos,
|
|
233
|
-
start = end && iter.toTagStart();
|
|
234
|
-
if (!start || cmpPos(iter, pos) > 0) {
|
|
235
|
-
return undefined;
|
|
236
|
-
}
|
|
237
|
-
const tag = start[2].toLowerCase(),
|
|
238
|
-
here = {from: iter.pos, to, tag};
|
|
239
|
-
if (end === 'selfClose' || voidTags.has(tag)) {
|
|
240
|
-
return {open: here, loc: 'self'};
|
|
241
|
-
} else if (start[1]) { // closing tag
|
|
242
|
-
return {open: iter.findMatchingOpen(tag), close: here, loc: 'close'};
|
|
243
|
-
}
|
|
244
|
-
// opening tag
|
|
245
|
-
return {open: here, close: new Iter(this, to).findMatchingClose(tag), loc: 'open'};
|
|
246
|
-
},
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
CodeMirror.defineExtension(
|
|
250
|
-
'findEnclosingTag',
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* @this {CodeMirror.Editor}
|
|
254
|
-
* @param {CodeMirror.Position} pos
|
|
255
|
-
* @param {string} tag
|
|
256
|
-
* @returns {CodeMirror.MatchingTagPair}
|
|
257
|
-
*/
|
|
258
|
-
function(pos, tag) {
|
|
259
|
-
const open = new Iter(this, pos).findMatchingOpen(tag);
|
|
260
|
-
if (open) {
|
|
261
|
-
const close = new Iter(this, pos).findMatchingClose(open.tag);
|
|
262
|
-
return close && {open, close};
|
|
263
|
-
}
|
|
264
|
-
return undefined;
|
|
265
|
-
},
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
/** Used by addon/edit/closetag.js */
|
|
269
|
-
CodeMirror.scanForClosingTag = (cm, pos, tagName) => new Iter(cm, pos).findMatchingClose(tagName);
|
|
270
|
-
|
|
271
|
-
CodeMirror.defineOption('matchTags', false, (cm, val, old) => {
|
|
272
|
-
if (old && old !== Init) {
|
|
273
|
-
cm.off('cursorActivity', doMatchTags);
|
|
274
|
-
clear(cm);
|
|
275
|
-
}
|
|
276
|
-
if (val) {
|
|
277
|
-
cm.on('cursorActivity', doMatchTags);
|
|
278
|
-
doMatchTags(cm);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* 清除高亮
|
|
284
|
-
* @param {CodeMirror.EditorWithMatchingTags} cm CodeMirror实例
|
|
285
|
-
*/
|
|
286
|
-
const clear = cm => {
|
|
287
|
-
if (cm.state.tagHit) {
|
|
288
|
-
cm.state.tagHit.clear();
|
|
289
|
-
cm.state.tagHit = undefined;
|
|
290
|
-
}
|
|
291
|
-
if (cm.state.tagOther) {
|
|
292
|
-
cm.state.tagOther.clear();
|
|
293
|
-
cm.state.tagOther = undefined;
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* 搜索并高亮匹配的标签
|
|
299
|
-
* @param {CodeMirror.EditorWithMatchingTags} cm CodeMirror实例
|
|
300
|
-
*/
|
|
301
|
-
const doMatchTags = cm => {
|
|
302
|
-
cm.operation(() => {
|
|
303
|
-
clear(cm);
|
|
304
|
-
if (cm.somethingSelected()) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const match = cm.findMatchingTag(cm.getCursor());
|
|
308
|
-
if (!match) {
|
|
309
|
-
return;
|
|
310
|
-
} else if (match.loc === 'self') {
|
|
311
|
-
cm.state.tagHit = cm.markText(match.open.from, match.open.to, {className: 'cm-matchingtag'});
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const hit = match.loc === 'open' ? match.open : match.close,
|
|
315
|
-
other = match.loc === 'close' ? match.open : match.close;
|
|
316
|
-
if (hit) {
|
|
317
|
-
cm.state.tagHit = cm.markText(hit.from, hit.to, {className: `cm-${other ? '' : 'non'}matchingtag`});
|
|
318
|
-
if (other) {
|
|
319
|
-
cm.state.tagOther = cm.markText(other.from, other.to, {className: 'cm-matchingtag'});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
};
|
|
324
|
-
})();
|
package/search.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @author Bhsd <https://github.com/bhsd-harry>
|
|
3
|
-
* @license GPL-3.0
|
|
4
|
-
*/
|
|
5
|
-
/* eslint-disable require-unicode-regexp */
|
|
6
|
-
(() => {
|
|
7
|
-
'use strict';
|
|
8
|
-
const {Pos} = CodeMirror,
|
|
9
|
-
{libs: {wphl: {msg, isPc, addons}}} = mw;
|
|
10
|
-
|
|
11
|
-
// Prepare elements
|
|
12
|
-
const $search = $('<input>', {class: 'Wikiplus-Quickedit-Search', placeholder: msg('search-placeholder')}),
|
|
13
|
-
$searchClose = $('<span>', {text: '×', class: 'Wikiplus-Symbol-Btn'}),
|
|
14
|
-
$searchNext = $('<span>', {text: '▼', class: 'Wikiplus-Symbol-Btn'}),
|
|
15
|
-
$searchPrev = $('<span>', {text: '▲', class: 'Wikiplus-Symbol-Btn'}),
|
|
16
|
-
$searchContainer = $('<div>', {
|
|
17
|
-
class: 'Wikiplus-Quickedit-Search-Div',
|
|
18
|
-
html: [$search, $searchNext, $searchPrev, $searchClose],
|
|
19
|
-
}),
|
|
20
|
-
$searchBtn = $('<span>', {class: 'Wikiplus-Btn', html: msg('addon-search')}),
|
|
21
|
-
$replace = $('<textarea>', {class: 'Wikiplus-Quickedit-Search', placeholder: msg('replace-placeholder'), rows: 1}),
|
|
22
|
-
$replaceClose = $('<span>', {text: '×', class: 'Wikiplus-Symbol-Btn'}),
|
|
23
|
-
$replaceNext = $('<span>', {text: '▼', class: 'Wikiplus-Symbol-Btn'}),
|
|
24
|
-
$replacePrev = $('<span>', {text: '▲', class: 'Wikiplus-Symbol-Btn'}),
|
|
25
|
-
$replaceContainer = $('<div>', {
|
|
26
|
-
class: 'Wikiplus-Quickedit-Search-Div',
|
|
27
|
-
html: [$replace, $replaceNext, $replacePrev, $replaceClose],
|
|
28
|
-
}),
|
|
29
|
-
$replaceBtn = $('<span>', {class: 'Wikiplus-Btn', html: msg('search-replace')});
|
|
30
|
-
|
|
31
|
-
const escapeRegExp = mw.util.escapeRegExp || mw.RegExp.escape;
|
|
32
|
-
const /** @type {CodeMirror.Mode<undefined>} */ overlay = {token: /** @override */ () => {}};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 根据搜索字符串生成高亮
|
|
36
|
-
* @param {string|RegExp} str 搜索字符串
|
|
37
|
-
*/
|
|
38
|
-
const token = str => {
|
|
39
|
-
const initial = typeof str === 'string' && new RegExp(`[^${escapeRegExp(str[0])}]`, 'i');
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @override
|
|
43
|
-
* @param {CodeMirror.StringStream} stream
|
|
44
|
-
*/
|
|
45
|
-
return stream => {
|
|
46
|
-
if (stream.match(str, true, true)) {
|
|
47
|
-
return 'search';
|
|
48
|
-
}
|
|
49
|
-
stream.next();
|
|
50
|
-
if (initial) {
|
|
51
|
-
stream.eatWhile(initial);
|
|
52
|
-
}
|
|
53
|
-
return undefined;
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/** input event handler of `$search` */
|
|
58
|
-
const onInput = () => {
|
|
59
|
-
$search.css('background-color', '').off('input', onInput);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
let /** @type {string} */ lastSource,
|
|
63
|
-
/** @type {string|RegExp} */ lastPtn,
|
|
64
|
-
/** @type {CodeMirror.SearchCursor} */ cursor;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* 更新搜索字符串
|
|
68
|
-
* @param {CodeMirror.Editor} cm CodeMirror实例
|
|
69
|
-
*/
|
|
70
|
-
const updatePtn = cm => {
|
|
71
|
-
const /** @type {string} */ source = $search.val();
|
|
72
|
-
if (!source) {
|
|
73
|
-
return undefined;
|
|
74
|
-
} else if (source === lastSource) {
|
|
75
|
-
return lastPtn;
|
|
76
|
-
}
|
|
77
|
-
const caseFold = source.endsWith('i'),
|
|
78
|
-
ptn = /^\/.+\/i?$/u.test(source)
|
|
79
|
-
? new RegExp(source.slice(1, caseFold ? -2 : -1), caseFold ? 'im' : 'm')
|
|
80
|
-
: source;
|
|
81
|
-
cm.removeOverlay(overlay);
|
|
82
|
-
overlay.token = token(ptn);
|
|
83
|
-
cm.addOverlay(overlay);
|
|
84
|
-
lastSource = source;
|
|
85
|
-
lastPtn = ptn;
|
|
86
|
-
cursor = cm.getSearchCursor(ptn, cm.getCursor(), {caseFold: true});
|
|
87
|
-
return ptn;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* keyboard event handler of `$search`
|
|
92
|
-
* @param {CodeMirror.Editor} cm CodeMirror实例
|
|
93
|
-
* @param {boolean} dir 搜索方向
|
|
94
|
-
* @param {boolean} update 是否先更新搜索字符串
|
|
95
|
-
*/
|
|
96
|
-
const findNext = (cm, dir, update = true) => {
|
|
97
|
-
if (update && !updatePtn(cm)) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
let result = dir ? cursor.findNext() : cursor.findPrevious();
|
|
101
|
-
if (!result) {
|
|
102
|
-
let pos;
|
|
103
|
-
if (dir) {
|
|
104
|
-
pos = Pos(0, 0);
|
|
105
|
-
} else {
|
|
106
|
-
const lastLine = cm.lastLine();
|
|
107
|
-
pos = Pos(lastLine, cm.getLine(lastLine).length);
|
|
108
|
-
}
|
|
109
|
-
cursor.pos = {from: pos, to: pos};
|
|
110
|
-
cursor.atOccurrence = false;
|
|
111
|
-
result = dir ? cursor.findNext() : cursor.findPrevious();
|
|
112
|
-
}
|
|
113
|
-
if (result) {
|
|
114
|
-
const from = cursor.from(),
|
|
115
|
-
to = cursor.to();
|
|
116
|
-
cm.setSelection(from, to);
|
|
117
|
-
cm.scrollIntoView({from, to});
|
|
118
|
-
onInput();
|
|
119
|
-
} else {
|
|
120
|
-
$search.css('background-color', 'pink').on('input', onInput);
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* replace one by one
|
|
126
|
-
* @param {CodeMirror.Editor} cm CodeMirror实例
|
|
127
|
-
* @param {boolean} dir 搜索方向
|
|
128
|
-
*/
|
|
129
|
-
const replaceNext = (cm, dir) => {
|
|
130
|
-
const ptn = updatePtn(cm);
|
|
131
|
-
if (!ptn) {
|
|
132
|
-
return;
|
|
133
|
-
} else if (cursor.atOccurrence) {
|
|
134
|
-
const replace = $replace.val();
|
|
135
|
-
cursor.replace(typeof ptn === 'string' ? replace : cursor.pos.match[0].replace(ptn, replace));
|
|
136
|
-
}
|
|
137
|
-
findNext(cm, dir, false);
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* keyboard event handler of `$replace`
|
|
142
|
-
* @param {CodeMirror.Editor} cm CodeMirror实例
|
|
143
|
-
*/
|
|
144
|
-
const replace = async cm => {
|
|
145
|
-
const ptn = updatePtn(cm);
|
|
146
|
-
if (!ptn) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const replacePtn = typeof ptn === 'string'
|
|
150
|
-
? new RegExp(escapeRegExp(ptn), 'gim')
|
|
151
|
-
: new RegExp(ptn, `g${ptn.flags}`),
|
|
152
|
-
val = cm.getValue(),
|
|
153
|
-
mt = val.match(replacePtn);
|
|
154
|
-
if (mt && await OO.ui.confirm(msg('replace-count', mt.length))) {
|
|
155
|
-
const {left, top} = cm.getScrollInfo();
|
|
156
|
-
cm.setValue(val.replace(replacePtn, $replace.val()));
|
|
157
|
-
cm.scrollTo(left, top);
|
|
158
|
-
$replaceContainer.hide();
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
/** click event handler of `$searchBtn` */
|
|
163
|
-
const findNew = () => {
|
|
164
|
-
$searchContainer.show();
|
|
165
|
-
$search.select().focus()[0]
|
|
166
|
-
.scrollIntoView({behavior: 'smooth'});
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
/** click event handler of `$replaceBtn` */
|
|
170
|
-
const replaceNew = () => {
|
|
171
|
-
$replaceContainer.show();
|
|
172
|
-
if ($searchContainer.is(':hidden')) {
|
|
173
|
-
findNew();
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* click event handler of `$searchClose`
|
|
179
|
-
* @param {CodeMirror.Editor} cm CodeMirror实例
|
|
180
|
-
*/
|
|
181
|
-
const reset = cm => {
|
|
182
|
-
cm.removeOverlay(overlay);
|
|
183
|
-
$searchContainer.hide();
|
|
184
|
-
$replaceContainer.hide();
|
|
185
|
-
lastSource = '';
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
CodeMirror.commands.find = findNew;
|
|
189
|
-
CodeMirror.commands.findNext = /** 向后搜索 */ doc => {
|
|
190
|
-
findNext(doc, true);
|
|
191
|
-
};
|
|
192
|
-
CodeMirror.commands.findPrev = /** 向前搜索 */ doc => {
|
|
193
|
-
findNext(doc, false);
|
|
194
|
-
};
|
|
195
|
-
CodeMirror.commands.replace = replaceNew;
|
|
196
|
-
CodeMirror.commands.replaceNext = /** 向后替换 */ doc => {
|
|
197
|
-
replaceNext(doc, true);
|
|
198
|
-
};
|
|
199
|
-
CodeMirror.commands.replaceAll = /** 全文替换 */ doc => { // eslint-disable-line es-x/no-string-prototype-replaceall
|
|
200
|
-
replace(doc);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
mw.hook('wiki-codemirror').add(/** @param {CodeMirror.Editor} cm */ cm => {
|
|
204
|
-
if (!cm.getOption('styleSelectedText') || addons.has('wikiEditor')) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const $textarea = $(cm.getWrapperElement()).prev('#Wikiplus-Quickedit');
|
|
208
|
-
if ($textarea.length === 0) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
$searchContainer.hide().insertBefore($textarea);
|
|
212
|
-
$searchBtn.click(findNew).insertAfter('#Wikiplus-Quickedit-Jump');
|
|
213
|
-
$searchClose.click(() => {
|
|
214
|
-
reset(cm);
|
|
215
|
-
});
|
|
216
|
-
$searchNext.click(() => {
|
|
217
|
-
findNext(cm, true);
|
|
218
|
-
});
|
|
219
|
-
$searchPrev.click(() => {
|
|
220
|
-
findNext(cm, false);
|
|
221
|
-
});
|
|
222
|
-
$search.val('').keydown(e => {
|
|
223
|
-
if (e.key === 'Enter') {
|
|
224
|
-
e.preventDefault();
|
|
225
|
-
findNext(cm, true);
|
|
226
|
-
} else if (e.key === 'Escape') {
|
|
227
|
-
e.stopPropagation();
|
|
228
|
-
reset(cm);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
$replaceContainer.hide().insertBefore($textarea);
|
|
232
|
-
$replaceBtn.click(replaceNew).insertAfter($searchBtn);
|
|
233
|
-
$replaceClose.click(() => {
|
|
234
|
-
$replaceContainer.hide();
|
|
235
|
-
});
|
|
236
|
-
$replaceNext.click(() => {
|
|
237
|
-
replaceNext(cm, true);
|
|
238
|
-
});
|
|
239
|
-
$replacePrev.click(() => {
|
|
240
|
-
replaceNext(cm, false);
|
|
241
|
-
});
|
|
242
|
-
$replace.val('').keydown(e => {
|
|
243
|
-
if (e.key === 'Enter') {
|
|
244
|
-
e.preventDefault();
|
|
245
|
-
replace(cm);
|
|
246
|
-
} else if (e.key === 'Escape') {
|
|
247
|
-
e.stopPropagation();
|
|
248
|
-
$replaceContainer.hide();
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
const ctrl = isPc(CodeMirror) ? 'Ctrl' : 'Cmd';
|
|
252
|
-
cm.addKeyMap({
|
|
253
|
-
[`${ctrl}-H`]: 'replace',
|
|
254
|
-
[`Shift-${ctrl}-H`]: 'replaceNext',
|
|
255
|
-
[`${ctrl}-Alt-Enter`]: 'replaceAll',
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
})();
|