@bhsd/codemirror-mediawiki 2.28.2 → 2.29.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 +15 -2
- package/dist/bidi.js +84 -0
- package/dist/codemirror.js +608 -0
- package/dist/color.js +52 -0
- package/dist/config.js +119 -0
- package/dist/css.js +30 -0
- package/dist/escape.js +35 -0
- package/dist/fold.js +445 -0
- package/dist/hover.js +52 -0
- package/dist/indent.js +50 -0
- package/dist/inlay.js +68 -0
- package/dist/javascript.js +5 -0
- package/dist/keybindings.js +35 -0
- package/dist/keymap.js +36 -0
- package/dist/linter.d.ts +1 -0
- package/dist/linter.js +134 -0
- package/dist/lua.js +428 -0
- package/dist/main.min.js +25 -29
- package/dist/matchBrackets.d.ts +7 -0
- package/dist/matchBrackets.js +58 -0
- package/dist/matchTag.js +139 -0
- package/dist/mediawiki.js +443 -0
- package/dist/mw.min.js +31 -35
- package/dist/openLinks.js +97 -0
- package/dist/ref.js +85 -0
- package/dist/signature.js +69 -0
- package/dist/static.js +46 -0
- package/dist/statusBar.js +138 -0
- package/dist/token.js +1888 -0
- package/dist/wiki.min.js +33 -37
- package/i18n/en.json +1 -1
- package/i18n/zh-hans.json +1 -1
- package/i18n/zh-hant.json +1 -1
- package/package.json +19 -18
- package/dist/keybindings.d.mts +0 -15
- package/dist/keybindings.mjs +0 -34
- package/dist/linter.d.mts +0 -43
- package/dist/linter.mjs +0 -132
- /package/dist/{mwConfig.d.mts → mwConfig.d.ts} +0 -0
- /package/dist/{mwConfig.mjs → mwConfig.js} +0 -0
package/dist/indent.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const noDetectionLangs = new Set(['plain', 'mediawiki', 'html']);
|
|
2
|
+
/**
|
|
3
|
+
* 检测文本的缩进方式
|
|
4
|
+
* @param text 文本内容
|
|
5
|
+
* @param defaultIndent 默认缩进方式
|
|
6
|
+
* @param lang 语言
|
|
7
|
+
*/
|
|
8
|
+
export const detectIndent = (text, defaultIndent, lang) => {
|
|
9
|
+
if (noDetectionLangs.has(lang)) {
|
|
10
|
+
return defaultIndent;
|
|
11
|
+
}
|
|
12
|
+
const lineSpaces = [];
|
|
13
|
+
let tabLines = 0;
|
|
14
|
+
for (const line of text.split('\n')) {
|
|
15
|
+
if (!line.trim()) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
let tabs = 0, spaces = 0;
|
|
19
|
+
for (const char of line) {
|
|
20
|
+
if (char === '\t') {
|
|
21
|
+
tabs++;
|
|
22
|
+
}
|
|
23
|
+
else if (char === ' ') {
|
|
24
|
+
spaces++;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (tabs && tabs * 8 >= spaces) {
|
|
31
|
+
tabLines++;
|
|
32
|
+
}
|
|
33
|
+
if (spaces > 1 && spaces >= tabs * 2) {
|
|
34
|
+
lineSpaces.push(spaces);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const { length } = lineSpaces;
|
|
38
|
+
if (tabLines > length) {
|
|
39
|
+
return '\t';
|
|
40
|
+
}
|
|
41
|
+
else if (tabLines === length) {
|
|
42
|
+
return defaultIndent;
|
|
43
|
+
}
|
|
44
|
+
for (let i = Math.min(...lineSpaces, 8); i > 2; i--) {
|
|
45
|
+
if (lineSpaces.every(s => s % i === 0)) {
|
|
46
|
+
return ' '.repeat(i);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return ' ';
|
|
50
|
+
};
|
package/dist/inlay.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { StateField, StateEffect } from '@codemirror/state';
|
|
2
|
+
import { Decoration, EditorView, WidgetType, ViewPlugin } from '@codemirror/view';
|
|
3
|
+
import { getLSP } from '@bhsd/common';
|
|
4
|
+
import { posToIndex } from './hover';
|
|
5
|
+
class InlayHintWidget extends WidgetType {
|
|
6
|
+
constructor(label) {
|
|
7
|
+
super();
|
|
8
|
+
this.label = label;
|
|
9
|
+
}
|
|
10
|
+
toDOM() {
|
|
11
|
+
const element = document.createElement('span');
|
|
12
|
+
element.textContent = this.label;
|
|
13
|
+
element.className = 'cm-inlay-hint';
|
|
14
|
+
return element;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const stateEffect = StateEffect.define(), field = StateField.define({
|
|
18
|
+
create() {
|
|
19
|
+
return Decoration.none;
|
|
20
|
+
},
|
|
21
|
+
update(deco, { state: { doc }, effects }) {
|
|
22
|
+
const str = doc.toString();
|
|
23
|
+
for (const effect of effects) {
|
|
24
|
+
if (effect.is(stateEffect)) {
|
|
25
|
+
const { value: { text, inlayHints } } = effect;
|
|
26
|
+
if (str === text) {
|
|
27
|
+
return inlayHints
|
|
28
|
+
? Decoration.set(inlayHints.reverse()
|
|
29
|
+
.map(({ position, label }) => [posToIndex(doc, position), label])
|
|
30
|
+
.sort(([a], [b]) => a - b)
|
|
31
|
+
.map(([index, label]) => Decoration.widget({ widget: new InlayHintWidget(label) }).range(index)))
|
|
32
|
+
: Decoration.none;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return deco;
|
|
37
|
+
},
|
|
38
|
+
provide(f) {
|
|
39
|
+
return EditorView.decorations.from(f);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const updateField = async ({ view, docChanged }) => {
|
|
43
|
+
if (docChanged) {
|
|
44
|
+
const text = view.state.doc.toString();
|
|
45
|
+
view.dispatch({
|
|
46
|
+
effects: stateEffect.of({
|
|
47
|
+
text,
|
|
48
|
+
inlayHints: await getLSP(view)?.provideInlayHints(text),
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export default (cm) => [
|
|
54
|
+
field,
|
|
55
|
+
ViewPlugin.fromClass(class {
|
|
56
|
+
constructor(view) {
|
|
57
|
+
const timer = setInterval(() => {
|
|
58
|
+
if (getLSP(view, false, cm.getWikiConfig)) {
|
|
59
|
+
clearInterval(timer);
|
|
60
|
+
void updateField({ view, docChanged: true });
|
|
61
|
+
}
|
|
62
|
+
}, 100);
|
|
63
|
+
}
|
|
64
|
+
update(update) {
|
|
65
|
+
void updateField(update);
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const keybindings = [
|
|
2
|
+
{ key: 'Ctrl-8', pre: '<blockquote>', post: '</blockquote>', desc: 'blockquote' },
|
|
3
|
+
{ key: 'Mod-.', pre: '<sup>', post: '</sup>', desc: 'sup' },
|
|
4
|
+
{ key: 'Mod-,', pre: '<sub>', post: '</sub>', desc: 'sub' },
|
|
5
|
+
{ key: 'Mod-Shift-6', pre: '<code>', post: '</code>', desc: 'code' },
|
|
6
|
+
{ key: 'Ctrl-Shift-5', pre: '<s>', post: '</s>', desc: 's' },
|
|
7
|
+
{ key: 'Mod-u', pre: '<u>', post: '</u>', desc: 'u' },
|
|
8
|
+
{ key: 'Mod-k', pre: '[[', post: ']]', desc: 'link' },
|
|
9
|
+
{ key: 'Mod-i', pre: "''", post: "''", desc: 'italic' },
|
|
10
|
+
{ key: 'Mod-b', pre: "'''", post: "'''", desc: 'bold' },
|
|
11
|
+
{ key: 'Mod-Shift-k', pre: '<ref>', post: '</ref>', desc: 'ref' },
|
|
12
|
+
{ key: 'Mod-/', pre: '<!-- ', post: ' -->', desc: 'comment' },
|
|
13
|
+
{ key: 'Ctrl-0', splitlines: true, desc: 'heading 0' },
|
|
14
|
+
...new Array(6).fill(0).map((_, i) => ({
|
|
15
|
+
key: `Ctrl-${i + 1}`,
|
|
16
|
+
pre: `${'='.repeat(i + 1)} `,
|
|
17
|
+
post: ` ${'='.repeat(i + 1)}`,
|
|
18
|
+
splitlines: true,
|
|
19
|
+
desc: `heading ${i + 1}`,
|
|
20
|
+
})),
|
|
21
|
+
{ key: 'Ctrl-7', pre: ' ', splitlines: true, desc: 'pre' },
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* 将文本各行包裹在指定的前后缀中
|
|
25
|
+
* @param text 跨行文本
|
|
26
|
+
* @param pre 前缀
|
|
27
|
+
* @param post 后缀
|
|
28
|
+
*/
|
|
29
|
+
export const encapsulateLines = (text, pre, post) => {
|
|
30
|
+
const lines = text.split('\n');
|
|
31
|
+
return lines.map(line => {
|
|
32
|
+
const str = (/^(={1,6})(.+)\1\s*$/u.exec(line)?.[2] ?? line).trim();
|
|
33
|
+
return pre === ' ' || lines.length === 1 || line.trim() ? pre + str + post : str;
|
|
34
|
+
}).join('\n');
|
|
35
|
+
};
|
package/dist/keymap.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { keymap } from '@codemirror/view';
|
|
2
|
+
import { EditorSelection } from '@codemirror/state';
|
|
3
|
+
import { keybindings, encapsulateLines } from './keybindings';
|
|
4
|
+
/**
|
|
5
|
+
* 生成keymap
|
|
6
|
+
* @param opt 快捷键设置
|
|
7
|
+
* @param opt.key 键名
|
|
8
|
+
* @param opt.pre 前缀
|
|
9
|
+
* @param opt.post 后缀
|
|
10
|
+
* @param opt.splitlines 是否分行
|
|
11
|
+
*/
|
|
12
|
+
const getKeymap = ({ key, pre = '', post = '', splitlines }) => ({
|
|
13
|
+
key,
|
|
14
|
+
run(view) {
|
|
15
|
+
const { state } = view;
|
|
16
|
+
view.dispatch(state.changeByRange(({ from, to }) => {
|
|
17
|
+
if (splitlines) {
|
|
18
|
+
const start = state.doc.lineAt(from).from, end = state.doc.lineAt(to).to, insert = encapsulateLines(state.sliceDoc(start, end), pre, post);
|
|
19
|
+
return {
|
|
20
|
+
range: EditorSelection.range(start, start + insert.length),
|
|
21
|
+
changes: { from: start, to: end, insert },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const insert = pre + state.sliceDoc(from, to) + post, head = from + insert.length;
|
|
25
|
+
return {
|
|
26
|
+
range: from === to
|
|
27
|
+
? EditorSelection.range(from + pre.length, head - post.length)
|
|
28
|
+
: EditorSelection.range(head, head),
|
|
29
|
+
changes: { from, to, insert },
|
|
30
|
+
};
|
|
31
|
+
}));
|
|
32
|
+
return true;
|
|
33
|
+
},
|
|
34
|
+
preventDefault: true,
|
|
35
|
+
});
|
|
36
|
+
export default keymap.of(keybindings.map(getKeymap));
|
package/dist/linter.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ declare interface JsonError {
|
|
|
32
32
|
* @param obj 对象
|
|
33
33
|
*/
|
|
34
34
|
export declare const getWikiLinter: getAsyncLinter<Promise<MixedDiagnostic[]>, Option, object>;
|
|
35
|
+
export declare const jsConfig: Linter.Config<Linter.RulesRecord, Linter.RulesRecord>;
|
|
35
36
|
/** 获取 ESLint */
|
|
36
37
|
export declare const getJsLinter: getAsyncLinter<Linter.LintMessage[]>;
|
|
37
38
|
/** 获取 Stylelint */
|
package/dist/linter.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-unreadable-iife */
|
|
2
|
+
import { loadScript, getWikiparse, getLSP, sanitizeInlineStyle } from '@bhsd/common';
|
|
3
|
+
import { styleLint } from '@bhsd/common/dist/stylelint';
|
|
4
|
+
/**
|
|
5
|
+
* 计算位置
|
|
6
|
+
* @param range 范围
|
|
7
|
+
* @param line 行号
|
|
8
|
+
* @param column 列号
|
|
9
|
+
*/
|
|
10
|
+
const offsetAt = (range, line, column) => {
|
|
11
|
+
if (line === -2) {
|
|
12
|
+
return range[0];
|
|
13
|
+
}
|
|
14
|
+
return line === 0 ? range[1] : range[0] + column;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 获取 Wikitext LSP
|
|
18
|
+
* @param opt 选项
|
|
19
|
+
* @param obj 对象
|
|
20
|
+
*/
|
|
21
|
+
export const getWikiLinter = async (opt, obj) => {
|
|
22
|
+
await getWikiparse(opt?.['getConfig'], opt?.['i18n']);
|
|
23
|
+
const lsp = getLSP(obj, opt?.['include']);
|
|
24
|
+
return async (text, config) => {
|
|
25
|
+
const diagnostics = (await lsp.provideDiagnostics(text)).filter(({ code, severity }) => Number(config?.[code] ?? 2) > Number(severity === 2)), tokens = 'findStyleTokens' in lsp && config?.['invalid-css'] !== '0' ? await lsp.findStyleTokens() : [];
|
|
26
|
+
if (tokens.length === 0) {
|
|
27
|
+
return diagnostics;
|
|
28
|
+
}
|
|
29
|
+
const cssLint = await getCssLinter();
|
|
30
|
+
return [
|
|
31
|
+
...diagnostics,
|
|
32
|
+
...(await cssLint(tokens.map(({ childNodes, type, tag }, i) => `${type === 'ext-attr' ? 'div' : tag}#${i}{\n${sanitizeInlineStyle(childNodes[1].childNodes[0].data)
|
|
33
|
+
.replace(/\n/gu, ' ')}\n}`).join('\n'))).map(({ line, column, endLine, endColumn, rule, severity, text: message }) => {
|
|
34
|
+
const i = Math.ceil(line / 3), { range } = tokens[i - 1].childNodes[1].childNodes[0], from = offsetAt(range, line - 3 * i, column - 1);
|
|
35
|
+
return {
|
|
36
|
+
from,
|
|
37
|
+
to: endLine === undefined ? from : offsetAt(range, endLine - 3 * i, endColumn - 1),
|
|
38
|
+
severity: severity === 'error' ? 1 : 2,
|
|
39
|
+
source: 'Stylelint',
|
|
40
|
+
code: rule,
|
|
41
|
+
message,
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export const jsConfig = /* #__PURE__ */ (() => ({
|
|
48
|
+
env: { browser: true, es2024: true, jquery: true },
|
|
49
|
+
globals: {
|
|
50
|
+
mw: 'readonly',
|
|
51
|
+
mediaWiki: 'readonly',
|
|
52
|
+
OO: 'readonly',
|
|
53
|
+
addOnloadHook: 'readonly',
|
|
54
|
+
importScriptURI: 'readonly',
|
|
55
|
+
importScript: 'readonly',
|
|
56
|
+
importStylesheet: 'readonly',
|
|
57
|
+
importStylesheetURI: 'readonly',
|
|
58
|
+
},
|
|
59
|
+
}))();
|
|
60
|
+
/** 获取 ESLint */
|
|
61
|
+
export const getJsLinter = async () => {
|
|
62
|
+
await loadScript('npm/@bhsd/eslint-browserify', 'eslint');
|
|
63
|
+
/** @see https://www.npmjs.com/package/@codemirror/lang-javascript */
|
|
64
|
+
const esLinter = new eslint.Linter(), conf = {
|
|
65
|
+
env: { browser: true, es2024: true },
|
|
66
|
+
parserOptions: { ecmaVersion: 15, sourceType: 'module' },
|
|
67
|
+
}, recommended = {};
|
|
68
|
+
for (const [name, { meta }] of esLinter.getRules()) {
|
|
69
|
+
if (meta?.docs?.recommended) {
|
|
70
|
+
recommended[name] = 2;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const linter = (text, opt) => {
|
|
74
|
+
const config = { ...conf, ...opt };
|
|
75
|
+
if (!('rules' in config)
|
|
76
|
+
|| config.extends === 'eslint:recommended'
|
|
77
|
+
|| Array.isArray(config.extends) && config.extends.includes('eslint:recommended')) {
|
|
78
|
+
config.rules = { ...recommended, ...config.rules };
|
|
79
|
+
}
|
|
80
|
+
delete config.extends;
|
|
81
|
+
linter.config = config;
|
|
82
|
+
return esLinter.verify(text, config);
|
|
83
|
+
};
|
|
84
|
+
linter.fixer = (code, rule) => esLinter.verifyAndFix(code, rule ? { ...linter.config, rules: { [rule]: linter.config.rules?.[rule] ?? 2 } } : linter.config).output;
|
|
85
|
+
return linter;
|
|
86
|
+
};
|
|
87
|
+
/** 获取 Stylelint */
|
|
88
|
+
export const getCssLinter = async () => {
|
|
89
|
+
await loadScript('npm/@bhsd/stylelint-browserify', 'stylelint');
|
|
90
|
+
const linter = async (code, opt) => {
|
|
91
|
+
const warnings = await styleLint(stylelint, code, opt);
|
|
92
|
+
if (opt && 'rules' in opt) {
|
|
93
|
+
linter.config = opt;
|
|
94
|
+
}
|
|
95
|
+
return warnings;
|
|
96
|
+
};
|
|
97
|
+
linter.fixer = (code, rule) => {
|
|
98
|
+
if (!linter.config) {
|
|
99
|
+
throw new Error('Fixer unavailable!');
|
|
100
|
+
}
|
|
101
|
+
return styleLint(stylelint, code, rule ? { extends: [], rules: { [rule]: linter.config.rules?.[rule] ?? true } } : linter.config, true);
|
|
102
|
+
};
|
|
103
|
+
return linter;
|
|
104
|
+
};
|
|
105
|
+
/** 获取 Luacheck */
|
|
106
|
+
export const getLuaLinter = async () => {
|
|
107
|
+
await loadScript('npm/luacheck-browserify', 'luacheck');
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
109
|
+
const luachecker = await luacheck(undefined);
|
|
110
|
+
return async (text) => (await luachecker.queue(text)).filter(({ severity }) => severity);
|
|
111
|
+
};
|
|
112
|
+
/** JSON.parse */
|
|
113
|
+
export const getJsonLinter = () => str => {
|
|
114
|
+
try {
|
|
115
|
+
if (str.trim()) {
|
|
116
|
+
JSON.parse(str);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
if (e instanceof SyntaxError) {
|
|
121
|
+
const { message } = e, line = /\bline (\d+)/u.exec(message)?.[1], column = /\bcolumn (\d+)/u.exec(message)?.[1], position = /\bposition (\d+)/u.exec(message)?.[1];
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
message,
|
|
125
|
+
severity: 'error',
|
|
126
|
+
line,
|
|
127
|
+
column,
|
|
128
|
+
position,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return [];
|
|
134
|
+
};
|