@bhsd/codemirror-mediawiki 2.28.2 → 2.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,608 @@
1
+ import { EditorView, lineNumbers, keymap, highlightSpecialChars, highlightActiveLine, highlightActiveLineGutter, highlightWhitespace, highlightTrailingWhitespace, drawSelection, scrollPastEnd, rectangularSelection, crosshairCursor, } from '@codemirror/view';
2
+ import { Compartment, EditorState, EditorSelection, SelectionRange } from '@codemirror/state';
3
+ import { syntaxHighlighting, defaultHighlightStyle, indentOnInput, indentUnit, ensureSyntaxTree, } from '@codemirror/language';
4
+ import { defaultKeymap, historyKeymap, history, redo, indentWithTab } from '@codemirror/commands';
5
+ import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
6
+ import { linter, lintGutter, lintKeymap } from '@codemirror/lint';
7
+ import { closeBrackets, autocompletion, acceptCompletion, completionKeymap, startCompletion, } from '@codemirror/autocomplete';
8
+ import { json } from '@codemirror/lang-json';
9
+ import { getLSP } from '@bhsd/common';
10
+ import colorPicker from './color';
11
+ import { mediawiki, html } from './mediawiki';
12
+ import escapeKeymap from './escape';
13
+ import codeFolding, { foldHandler } from './fold';
14
+ import tagMatchingState from './matchTag';
15
+ import refHover from './ref';
16
+ import magicWordHover, { posToIndex } from './hover';
17
+ import signatureHelp from './signature';
18
+ import inlayHints from './inlay';
19
+ import { getWikiLinter, getJsLinter, getCssLinter, getLuaLinter, getJsonLinter } from './linter';
20
+ import openLinks from './openLinks';
21
+ import { tagModes, getStaticMwConfig } from './static';
22
+ import bidiIsolation from './bidi';
23
+ import toolKeymap from './keymap';
24
+ import statusBar from './statusBar';
25
+ import { detectIndent, noDetectionLangs } from './indent';
26
+ import bracketMatching from './matchBrackets';
27
+ import javascript from './javascript';
28
+ import css from './css';
29
+ import lua from './lua';
30
+ const plain = () => EditorView.contentAttributes.of({ spellcheck: 'true' });
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ const languages = {
33
+ plain,
34
+ mediawiki(config) {
35
+ return [
36
+ mediawiki(config),
37
+ plain(),
38
+ bidiIsolation,
39
+ toolKeymap,
40
+ ];
41
+ },
42
+ html,
43
+ javascript,
44
+ css,
45
+ json,
46
+ lua,
47
+ };
48
+ function mediawikiOnly(ext) {
49
+ return typeof ext === 'function'
50
+ ? [(enable, cm) => enable ? ext(cm) : [], { mediawiki: true }]
51
+ : [(e = []) => e, { mediawiki: ext }];
52
+ }
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ const avail = {
55
+ highlightSpecialChars: [highlightSpecialChars, {}],
56
+ highlightActiveLine: [highlightActiveLine, {}],
57
+ highlightWhitespace: [highlightWhitespace, {}],
58
+ highlightTrailingWhitespace: [highlightTrailingWhitespace, {}],
59
+ highlightSelectionMatches: [highlightSelectionMatches, {}],
60
+ bracketMatching: [bracketMatching, { mediawiki: { brackets: '()[]{}()【】[]{}' } }],
61
+ closeBrackets: [closeBrackets, {}],
62
+ scrollPastEnd: [scrollPastEnd, {}],
63
+ openLinks: [(enable, cm) => enable ? openLinks(cm) : [], { mediawiki: true }],
64
+ allowMultipleSelections: [
65
+ () => [
66
+ EditorState.allowMultipleSelections.of(true),
67
+ drawSelection(),
68
+ rectangularSelection(),
69
+ crosshairCursor(),
70
+ ],
71
+ {},
72
+ ],
73
+ autocompletion: [
74
+ () => [
75
+ autocompletion({ defaultKeymap: false }),
76
+ keymap.of([
77
+ ...completionKeymap.filter(({ run }) => run !== startCompletion),
78
+ { key: 'Shift-Enter', run: startCompletion },
79
+ { key: 'Tab', run: acceptCompletion },
80
+ ]),
81
+ ],
82
+ {},
83
+ ],
84
+ codeFolding,
85
+ colorPicker,
86
+ escape: mediawikiOnly(keymap.of(escapeKeymap)),
87
+ tagMatching: mediawikiOnly(tagMatchingState),
88
+ refHover: mediawikiOnly(refHover),
89
+ hover: mediawikiOnly(magicWordHover),
90
+ signatureHelp: mediawikiOnly(signatureHelp),
91
+ inlayHints: mediawikiOnly(inlayHints),
92
+ };
93
+ const linters = {};
94
+ const phrases = {};
95
+ /**
96
+ * 获取指定行列的位置
97
+ * @param doc 文档
98
+ * @param line 行号
99
+ * @param column 列号
100
+ */
101
+ const pos = (doc, line, column) => posToIndex(doc, { line: line - 1, character: column - 1 });
102
+ /** CodeMirror 6 编辑器 */
103
+ export class CodeMirror6 {
104
+ #textarea;
105
+ #language = new Compartment();
106
+ #linter = new Compartment();
107
+ #extensions = new Compartment();
108
+ #dir = new Compartment();
109
+ #indent = new Compartment();
110
+ #extraKeys = new Compartment();
111
+ #phrases = new Compartment();
112
+ #lineWrapping = new Compartment();
113
+ #view;
114
+ #lang;
115
+ #visible = false;
116
+ #preferred = new Set();
117
+ #indentStr = '\t';
118
+ get textarea() {
119
+ return this.#textarea;
120
+ }
121
+ get view() {
122
+ return this.#view;
123
+ }
124
+ get lang() {
125
+ return this.#lang;
126
+ }
127
+ get visible() {
128
+ return this.#visible && this.textarea.isConnected;
129
+ }
130
+ /**
131
+ * @param textarea 文本框
132
+ * @param lang 语言
133
+ * @param config 语言设置
134
+ * @param init 是否初始化
135
+ */
136
+ constructor(textarea, lang = 'plain', config, init = true) {
137
+ this.#textarea = textarea;
138
+ this.#lang = lang;
139
+ if (init) {
140
+ this.initialize(config);
141
+ }
142
+ }
143
+ /**
144
+ * 初始化编辑器
145
+ * @param config 语言设置
146
+ */
147
+ initialize(config) {
148
+ let timer;
149
+ const { textarea, lang } = this, { value, dir: d, accessKey, tabIndex, lang: l, readOnly } = textarea, extensions = [
150
+ this.#language.of(languages[lang](config)),
151
+ this.#linter.of(linters[lang] ?? []),
152
+ this.#extensions.of([]),
153
+ this.#dir.of(EditorView.editorAttributes.of({ dir: d })),
154
+ this.#extraKeys.of([]),
155
+ this.#phrases.of(EditorState.phrases.of(phrases)),
156
+ this.#lineWrapping.of(EditorView.lineWrapping),
157
+ syntaxHighlighting(defaultHighlightStyle),
158
+ EditorView.contentAttributes.of({
159
+ accesskey: accessKey,
160
+ tabindex: String(tabIndex),
161
+ }),
162
+ EditorView.editorAttributes.of({ lang: l }),
163
+ lineNumbers(),
164
+ highlightActiveLineGutter(),
165
+ keymap.of([
166
+ ...defaultKeymap,
167
+ ...searchKeymap,
168
+ {
169
+ key: 'Mod-Shift-x',
170
+ run: () => {
171
+ const dir = textarea.dir === 'rtl' ? 'ltr' : 'rtl';
172
+ textarea.dir = dir;
173
+ this.#effects(this.#dir.reconfigure(EditorView.editorAttributes.of({ dir })));
174
+ return true;
175
+ },
176
+ },
177
+ ]),
178
+ EditorView.theme({
179
+ '.cm-panels': { direction: document.dir },
180
+ }),
181
+ EditorView.updateListener.of(({ state: { doc }, startState: { doc: startDoc }, docChanged, focusChanged, }) => {
182
+ if (docChanged) {
183
+ clearTimeout(timer);
184
+ timer = setTimeout(() => {
185
+ textarea.value = doc.toString();
186
+ textarea.dispatchEvent(new Event('input'));
187
+ }, 400);
188
+ if (!noDetectionLangs.has(this.lang) && !startDoc.toString().trim()) {
189
+ this.setIndent(detectIndent(doc.toString(), this.#indentStr, this.lang));
190
+ }
191
+ }
192
+ if (focusChanged) {
193
+ textarea.dispatchEvent(new Event(this.#view.hasFocus ? 'focus' : 'blur'));
194
+ }
195
+ }),
196
+ ...readOnly
197
+ ? [EditorState.readOnly.of(true)]
198
+ : [
199
+ history(),
200
+ indentOnInput(),
201
+ this.#indent.of(indentUnit.of(detectIndent(value, this.#indentStr, lang))),
202
+ keymap.of([
203
+ ...historyKeymap,
204
+ indentWithTab,
205
+ { win: 'Ctrl-Shift-z', run: redo, preventDefault: true },
206
+ ]),
207
+ ],
208
+ ];
209
+ this.#view = new EditorView({
210
+ extensions,
211
+ doc: value,
212
+ });
213
+ const { fontSize, lineHeight, border } = getComputedStyle(textarea);
214
+ textarea.before(this.#view.dom);
215
+ this.#minHeight();
216
+ this.#view.dom.style.border = border;
217
+ this.#view.scrollDOM.style.fontSize = fontSize;
218
+ this.#view.scrollDOM.style.lineHeight = lineHeight;
219
+ this.toggle(true);
220
+ this.#view.dom.addEventListener('click', foldHandler(this.#view));
221
+ this.prefer({});
222
+ }
223
+ /**
224
+ * 修改扩展
225
+ * @param effects 扩展变动
226
+ */
227
+ #effects(effects) {
228
+ this.#view.dispatch({ effects });
229
+ }
230
+ /**
231
+ * 设置编辑器最小高度
232
+ * @param linting 是否启用语法检查
233
+ */
234
+ #minHeight(linting) {
235
+ this.#view.dom.style.minHeight = linting ? 'calc(100px + 2em)' : '2em';
236
+ }
237
+ /** 获取语法检查扩展 */
238
+ #getLintExtension() {
239
+ return this.#linter.get(this.#view.state)[0];
240
+ }
241
+ /**
242
+ * 设置语言
243
+ * @param lang 语言
244
+ * @param config 语言设置
245
+ */
246
+ setLanguage(lang = 'plain', config) {
247
+ this.#lang = lang;
248
+ if (this.#view) {
249
+ this.#effects([
250
+ this.#language.reconfigure(languages[lang](config)),
251
+ this.#linter.reconfigure(linters[lang] ?? []),
252
+ ]);
253
+ this.#minHeight(Boolean(linters[lang]));
254
+ this.prefer({});
255
+ }
256
+ }
257
+ /**
258
+ * 开始语法检查
259
+ * @param lintSource 语法检查函数
260
+ */
261
+ lint(lintSource) {
262
+ const linterExtension = lintSource
263
+ ? [
264
+ linter(view => lintSource(view.state.doc)),
265
+ lintGutter(),
266
+ keymap.of(lintKeymap),
267
+ statusBar(lintSource.fixer),
268
+ ]
269
+ : [];
270
+ if (lintSource) {
271
+ linters[this.#lang] = linterExtension;
272
+ }
273
+ else {
274
+ delete linters[this.#lang];
275
+ }
276
+ if (this.#view) {
277
+ this.#effects(this.#linter.reconfigure(linterExtension));
278
+ this.#minHeight(Boolean(lintSource));
279
+ }
280
+ }
281
+ /** 立即更新语法检查 */
282
+ update() {
283
+ if (this.#view) {
284
+ const extension = this.#getLintExtension();
285
+ if (extension) {
286
+ const plugin = this.#view.plugin(extension[1]);
287
+ plugin.set = true;
288
+ plugin.force();
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * 添加扩展
294
+ * @param names 扩展名
295
+ */
296
+ prefer(names) {
297
+ if (Array.isArray(names)) {
298
+ this.#preferred = new Set(names.filter(name => avail[name]));
299
+ }
300
+ else {
301
+ for (const [name, enable] of Object.entries(names)) {
302
+ if (enable && avail[name]) {
303
+ this.#preferred.add(name);
304
+ }
305
+ else {
306
+ this.#preferred.delete(name);
307
+ }
308
+ }
309
+ }
310
+ if (this.#view) {
311
+ this.#effects(this.#extensions.reconfigure([...this.#preferred].map(name => {
312
+ const [extension, configs] = avail[name];
313
+ return extension(configs[this.#lang], this);
314
+ })));
315
+ }
316
+ }
317
+ /**
318
+ * 设置缩进
319
+ * @param indent 缩进字符串
320
+ */
321
+ setIndent(indent) {
322
+ if (this.#view) {
323
+ this.#effects(this.#indent.reconfigure(indentUnit.of(indent)));
324
+ }
325
+ else {
326
+ this.#indentStr = indent;
327
+ }
328
+ }
329
+ /**
330
+ * 设置文本换行
331
+ * @param wrapping 是否换行
332
+ */
333
+ setLineWrapping(wrapping) {
334
+ if (this.#view) {
335
+ this.#effects(this.#lineWrapping.reconfigure(wrapping ? EditorView.lineWrapping : []));
336
+ }
337
+ }
338
+ /**
339
+ * 获取默认linter
340
+ * @param opt 选项
341
+ */
342
+ async getLinter(opt) {
343
+ const isFunc = typeof opt === 'function', getOpt = (runtime) => isFunc ? opt(runtime) : opt;
344
+ switch (this.#lang) {
345
+ case 'mediawiki': {
346
+ const wikiLint = await getWikiLinter(getOpt(), this.#view);
347
+ return async (doc) => (await wikiLint(doc.toString(), getOpt(true)))
348
+ .map(({ severity, code, message, range: r, from, to, data = [], source }) => ({
349
+ source: source,
350
+ from: from ?? posToIndex(doc, r.start),
351
+ to: to ?? posToIndex(doc, r.end),
352
+ severity: severity === 2 ? 'warning' : 'error',
353
+ message: source === 'Stylelint' ? message : `${message} (${code})`,
354
+ actions: data.map(({ title, range, newText }) => ({
355
+ name: title,
356
+ apply(view) {
357
+ view.dispatch({
358
+ changes: {
359
+ from: posToIndex(doc, range.start),
360
+ to: posToIndex(doc, range.end),
361
+ insert: newText,
362
+ },
363
+ });
364
+ },
365
+ })),
366
+ }));
367
+ }
368
+ case 'javascript': {
369
+ const esLint = await getJsLinter();
370
+ const lintSource = doc => esLint(doc.toString(), getOpt())
371
+ .map(({ ruleId, message, severity, line, column, endLine, endColumn, fix, suggestions = [] }) => {
372
+ const start = pos(doc, line, column), diagnostic = {
373
+ source: 'ESLint',
374
+ message: message + (ruleId ? ` (${ruleId})` : ''),
375
+ severity: severity === 1 ? 'warning' : 'error',
376
+ from: start,
377
+ to: endLine === undefined ? start + 1 : pos(doc, endLine, endColumn),
378
+ };
379
+ if (fix || suggestions.length > 0) {
380
+ diagnostic.actions = [
381
+ ...fix ? [{ name: 'fix', fix }] : [],
382
+ ...suggestions.map(suggestion => ({ name: 'suggestion', fix: suggestion.fix })),
383
+ ].map(({ name, fix: { range: [from, to], text } }) => ({
384
+ name,
385
+ apply(view) {
386
+ view.dispatch({ changes: { from, to, insert: text } });
387
+ },
388
+ }));
389
+ }
390
+ return diagnostic;
391
+ });
392
+ lintSource.fixer = (doc, rule) => esLint.fixer(doc.toString(), rule);
393
+ return lintSource;
394
+ }
395
+ case 'css': {
396
+ const styleLint = await getCssLinter();
397
+ let option = getOpt() ?? {};
398
+ if (!('extends' in option || 'rules' in option)) {
399
+ option = { rules: option };
400
+ }
401
+ if (this.dialect === 'sanitized-css') {
402
+ const rules = option['rules'];
403
+ option = {
404
+ ...option,
405
+ rules: {
406
+ ...rules,
407
+ 'property-no-vendor-prefix': [
408
+ true,
409
+ {
410
+ ignoreProperties: ['user-select'],
411
+ },
412
+ ],
413
+ 'property-disallowed-list': [
414
+ ...rules?.['property-disallowed-list'] ?? [],
415
+ '/^--/',
416
+ ],
417
+ },
418
+ };
419
+ }
420
+ const lintSource = async (doc) => (await styleLint(doc.toString(), option))
421
+ .map(({ text, severity, line, column, endLine, endColumn, fix }) => {
422
+ const diagnostic = {
423
+ source: 'Stylelint',
424
+ message: text,
425
+ severity,
426
+ from: pos(doc, line, column),
427
+ to: endLine === undefined ? doc.line(line).to : pos(doc, endLine, endColumn),
428
+ };
429
+ if (fix) {
430
+ diagnostic.actions = [
431
+ {
432
+ name: 'fix',
433
+ apply(view) {
434
+ view.dispatch({
435
+ changes: { from: fix.range[0], to: fix.range[1], insert: fix.text },
436
+ });
437
+ },
438
+ },
439
+ ];
440
+ }
441
+ return diagnostic;
442
+ });
443
+ lintSource.fixer = async (doc, rule) => styleLint.fixer(doc.toString(), rule);
444
+ return lintSource;
445
+ }
446
+ case 'lua': {
447
+ const luaLint = await getLuaLinter();
448
+ return async (doc) => (await luaLint(doc.toString()))
449
+ .map(({ line, column, end_column: endColumn, msg: message, severity }) => ({
450
+ source: 'Luacheck',
451
+ message,
452
+ severity: severity === 1 ? 'warning' : 'error',
453
+ from: pos(doc, line, column),
454
+ to: pos(doc, line, endColumn + 1),
455
+ }));
456
+ }
457
+ case 'json': {
458
+ const jsonLint = getJsonLinter();
459
+ return doc => {
460
+ const [e] = jsonLint(doc.toString());
461
+ if (e) {
462
+ const { message, severity, line, column, position } = e;
463
+ let from = 0;
464
+ if (position) {
465
+ from = Number(position);
466
+ }
467
+ else if (line && column) {
468
+ from = pos(doc, Number(line), Number(column));
469
+ }
470
+ return [{ message, severity, from, to: from }];
471
+ }
472
+ return [];
473
+ };
474
+ }
475
+ default:
476
+ return undefined;
477
+ }
478
+ }
479
+ /**
480
+ * 重设编辑器内容
481
+ * @param insert 新内容
482
+ */
483
+ setContent(insert) {
484
+ if (this.#view) {
485
+ this.#view.dispatch({
486
+ changes: { from: 0, to: this.#view.state.doc.length, insert },
487
+ });
488
+ }
489
+ }
490
+ /**
491
+ * 在编辑器和文本框之间切换
492
+ * @param show 是否显示编辑器
493
+ */
494
+ toggle(show = !this.#visible) {
495
+ if (!this.#view) {
496
+ return;
497
+ }
498
+ else if (show && !this.#visible) {
499
+ const { value, selectionStart, selectionEnd, scrollTop, offsetHeight, style: { height } } = this.#textarea, hasFocus = document.activeElement === this.#textarea;
500
+ this.setContent(value);
501
+ this.#view.dom.style.height = offsetHeight ? `${offsetHeight}px` : height;
502
+ this.#view.dom.style.removeProperty('display');
503
+ this.#textarea.style.display = 'none';
504
+ this.#view.requestMeasure();
505
+ this.#view.dispatch({
506
+ selection: { anchor: selectionStart, head: selectionEnd },
507
+ });
508
+ if (hasFocus) {
509
+ this.#view.focus();
510
+ }
511
+ requestAnimationFrame(() => {
512
+ this.#view.scrollDOM.scrollTop = scrollTop;
513
+ });
514
+ }
515
+ else if (!show && this.#visible) {
516
+ const { state: { selection: { main: { from, to, head } } }, hasFocus } = this.#view, { scrollDOM: { scrollTop } } = this.#view;
517
+ this.#view.dom.style.setProperty('display', 'none', 'important');
518
+ this.#textarea.style.display = '';
519
+ this.#textarea.setSelectionRange(from, to, head === to ? 'forward' : 'backward');
520
+ if (hasFocus) {
521
+ this.#textarea.focus();
522
+ }
523
+ requestAnimationFrame(() => {
524
+ this.#textarea.scrollTop = scrollTop;
525
+ });
526
+ }
527
+ this.#visible = show;
528
+ }
529
+ /** 销毁实例 */
530
+ destroy() {
531
+ if (this.visible) {
532
+ this.toggle(false);
533
+ }
534
+ if (this.#view) {
535
+ getLSP(this.#view)?.destroy();
536
+ this.#view.destroy();
537
+ }
538
+ Object.setPrototypeOf(this, null);
539
+ }
540
+ /**
541
+ * 添加额外快捷键
542
+ * @param keys 快捷键
543
+ */
544
+ extraKeys(keys) {
545
+ if (this.#view) {
546
+ this.#effects(this.#extraKeys.reconfigure(keymap.of(keys)));
547
+ }
548
+ }
549
+ /**
550
+ * 设置翻译信息
551
+ * @param messages 翻译信息
552
+ */
553
+ localize(messages) {
554
+ Object.assign(phrases, messages);
555
+ if (this.#view) {
556
+ this.#effects(this.#phrases.reconfigure(EditorState.phrases.of(phrases)));
557
+ }
558
+ }
559
+ /**
560
+ * 获取语法树节点
561
+ * @param position 位置
562
+ */
563
+ getNodeAt(position) {
564
+ return this.#view && ensureSyntaxTree(this.#view.state, position)?.resolve(position, 1);
565
+ }
566
+ /**
567
+ * 滚动至指定位置
568
+ * @param position 位置
569
+ */
570
+ scrollTo(position) {
571
+ if (this.#view) {
572
+ const r = position ?? this.#view.state.selection.main, effects = EditorView.scrollIntoView(typeof r === 'number' || r instanceof SelectionRange
573
+ ? r
574
+ : EditorSelection.range(r.anchor, r.head));
575
+ effects.value.isSnapshot = true;
576
+ this.#view.dispatch({ effects });
577
+ }
578
+ }
579
+ /**
580
+ * 替换选中内容
581
+ * @param view
582
+ * @param func 替换函数
583
+ */
584
+ static replaceSelections(view, func) {
585
+ const { state } = view;
586
+ view.dispatch(state.changeByRange(({ from, to }) => {
587
+ const result = func(state.sliceDoc(from, to), { from, to });
588
+ if (typeof result === 'string') {
589
+ return {
590
+ range: EditorSelection.range(from, from + result.length),
591
+ changes: { from, to, insert: result },
592
+ };
593
+ }
594
+ const [insert, start, end = start] = result;
595
+ return {
596
+ range: EditorSelection.range(start, end),
597
+ changes: { from, to, insert },
598
+ };
599
+ }));
600
+ }
601
+ /**
602
+ * 将wikiparser-node设置转换为codemirror-mediawiki设置
603
+ * @param config
604
+ */
605
+ static getMwConfig(config) {
606
+ return getStaticMwConfig(config, tagModes);
607
+ }
608
+ }
package/dist/color.js ADDED
@@ -0,0 +1,52 @@
1
+ import { splitColors, numToHex } from '@bhsd/common';
2
+ import { EditorView } from '@codemirror/view';
3
+ import { parseCallExpression, parseColorLiteral, ColorType, colorPicker, colorPickerTheme, makeColorPicker, wrapperClassName, } from '@bhsd/codemirror-css-color-picker';
4
+ export const discoverColors = (_, from, to, type, doc) => {
5
+ if (!/mw-(?:(?:ext|html)tag-attribute-value|table-definition)/u.test(type)
6
+ && (!/mw-(?:template|parserfunction)(?:$|_)/u.test(type)
7
+ || !/[|=]/u.test(doc.sliceString(from - 1, from))
8
+ || !/[|\n]/u.test(doc.sliceString(to, to + 1)) && doc.sliceString(to, to + 2) !== '}}')
9
+ && (!/mw-templatevariable(?:$|_)/u.test(type)
10
+ || doc.sliceString(from - 1, from) !== '|'
11
+ || doc.sliceString(to, to + 1) !== '|' && doc.sliceString(to, to + 3) !== '}}}')) {
12
+ return null;
13
+ }
14
+ return splitColors(doc.sliceString(from, to)).filter(([, , , isColor]) => isColor)
15
+ .map(([s, start, end]) => {
16
+ const color = s.startsWith('#') ? parseColorLiteral(s) : parseCallExpression(s);
17
+ let alpha = color?.alpha;
18
+ if (color?.colorType !== ColorType.hex) {
19
+ alpha &&= numToHex(parseFloat(alpha.slice(1)) / (alpha.endsWith('%') ? 100 : 1));
20
+ }
21
+ return color && {
22
+ ...color,
23
+ colorType: ColorType.hex,
24
+ alpha: alpha ?? '',
25
+ from: from + start,
26
+ to: from + end,
27
+ };
28
+ }).filter(Boolean);
29
+ };
30
+ export default [
31
+ ([e, style] = []) => e
32
+ ? [
33
+ e,
34
+ EditorView.theme({
35
+ [`.${wrapperClassName}`]: {
36
+ outline: 'none',
37
+ ...style,
38
+ },
39
+ [`.${wrapperClassName} input[type="color"]`]: {
40
+ outline: '1px solid #eee',
41
+ },
42
+ }),
43
+ ]
44
+ : [],
45
+ {
46
+ css: [colorPicker],
47
+ mediawiki: [
48
+ [makeColorPicker({ discoverColors }), colorPickerTheme],
49
+ { marginLeft: '0.6ch' },
50
+ ],
51
+ },
52
+ ];