@bhsd/codemirror-mediawiki 2.19.4 → 2.19.6
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 +5 -4
- package/dist/bidi.d.ts +2 -1
- package/dist/codemirror.d.ts +4 -27
- package/dist/config.d.ts +0 -1
- package/dist/fold.d.ts +8 -1
- package/dist/linter.mjs +116 -0
- package/dist/main.min.js +14 -14
- package/dist/matchTag.d.ts +4 -3
- package/dist/mw.min.js +19 -19
- package/dist/mwConfig.mjs +161 -0
- package/dist/openExtLinks.d.ts +1 -1
- package/dist/openLinks.d.ts +4 -0
- package/dist/ref.d.ts +0 -5
- package/dist/static.d.ts +1 -1
- package/dist/token.d.ts +17 -6
- package/dist/wiki.min.js +19 -19
- package/i18n/en.json +1 -1
- package/i18n/zh-hans.json +1 -1
- package/i18n/zh-hant.json +1 -1
- package/mediawiki.css +0 -11
- package/mw/config.ts +180 -0
- package/package.json +10 -4
- package/src/linter.ts +149 -0
- package/src/static.ts +66 -0
package/i18n/en.json
CHANGED
package/i18n/zh-hans.json
CHANGED
package/i18n/zh-hant.json
CHANGED
package/mediawiki.css
CHANGED
|
@@ -29,17 +29,6 @@
|
|
|
29
29
|
.cm-focused .cm-nonmatchingTag {
|
|
30
30
|
background-color: rgba(187, 85, 85, .27);
|
|
31
31
|
}
|
|
32
|
-
.cm-mw-template-name,
|
|
33
|
-
.cm-mw-link-pagename,
|
|
34
|
-
.cm-mw-parserfunction.cm-mw-pagename,
|
|
35
|
-
.cm-mw-exttag-attribute-value.cm-mw-pagename,
|
|
36
|
-
.cm-mw-extlink-protocol,
|
|
37
|
-
.cm-mw-extlink,
|
|
38
|
-
.cm-mw-free-extlink-protocol,
|
|
39
|
-
.cm-mw-free-extlink,
|
|
40
|
-
.cm-mw-magic-link {
|
|
41
|
-
cursor: var(--codemirror-cursor);
|
|
42
|
-
}
|
|
43
32
|
#cm-preference > .oo-ui-window-frame {
|
|
44
33
|
height: 100% !important;
|
|
45
34
|
}
|
package/mw/config.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {CDN, setObject, getObject} from '@bhsd/common';
|
|
2
|
+
import {getStaticMwConfig} from '../src/static';
|
|
3
|
+
import type {Config} from 'wikiparser-node';
|
|
4
|
+
import type {MwConfig} from '../src/token';
|
|
5
|
+
|
|
6
|
+
declare interface MagicWord {
|
|
7
|
+
name: string;
|
|
8
|
+
aliases: string[];
|
|
9
|
+
'case-sensitive': boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare type MagicRule = (word: MagicWord) => boolean;
|
|
13
|
+
|
|
14
|
+
// 和本地缓存有关的常数
|
|
15
|
+
const ALL_SETTINGS_CACHE: Record<string, {time: number, config: MwConfig}> =
|
|
16
|
+
getObject('InPageEditMwConfig') ?? {},
|
|
17
|
+
SITE_ID = typeof mw === 'object' ? mw.config.get('wgServerName') + mw.config.get('wgScriptPath') : location.origin,
|
|
18
|
+
SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID],
|
|
19
|
+
VALID = Number(SITE_SETTINGS?.time) > Date.now() - 86_400 * 1000 * 30;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 将魔术字信息转换为CodeMirror接受的设置
|
|
23
|
+
* @param magicWords 完整魔术字列表
|
|
24
|
+
* @param rule 过滤函数
|
|
25
|
+
* @param flip 是否反向筛选对大小写敏感的魔术字
|
|
26
|
+
*/
|
|
27
|
+
const getConfig = (magicWords: MagicWord[], rule: MagicRule, flip?: boolean): Record<string, string> =>
|
|
28
|
+
Object.fromEntries(
|
|
29
|
+
magicWords.filter(rule).filter(({'case-sensitive': i}) => i !== flip)
|
|
30
|
+
.flatMap(({aliases, name, 'case-sensitive': i}) => aliases.map(alias => ({
|
|
31
|
+
alias: (i ? alias : alias.toLowerCase()).replace(/:$/u, ''),
|
|
32
|
+
name,
|
|
33
|
+
})))
|
|
34
|
+
.map(({alias, name}) => [alias, name]),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 将魔术字信息转换为CodeMirror接受的设置
|
|
39
|
+
* @param magicWords 完整魔术字列表
|
|
40
|
+
* @param rule 过滤函数
|
|
41
|
+
*/
|
|
42
|
+
const getConfigPair = (magicWords: MagicWord[], rule: MagicRule): [Record<string, string>, Record<string, string>] =>
|
|
43
|
+
[true, false].map(bool => getConfig(magicWords, rule, bool)) as [Record<string, string>, Record<string, string>];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 将设置保存到mw.config
|
|
47
|
+
* @param config 设置
|
|
48
|
+
*/
|
|
49
|
+
const setConfig = (config: MwConfig): void => {
|
|
50
|
+
mw.config.set('extCodeMirrorConfig', config);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 加载CodeMirror的mediawiki模块需要的设置
|
|
55
|
+
* @param modes tagModes
|
|
56
|
+
*/
|
|
57
|
+
export const getMwConfig = async (modes: Record<string, string>): Promise<MwConfig> => {
|
|
58
|
+
if (mw.loader.getState('ext.CodeMirror') !== null && !VALID) { // 只在localStorage过期时才会重新加载ext.CodeMirror.data
|
|
59
|
+
await mw.loader.using(mw.loader.getState('ext.CodeMirror.data') ? 'ext.CodeMirror.data' : 'ext.CodeMirror');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let config = mw.config.get('extCodeMirrorConfig') as MwConfig | null;
|
|
63
|
+
if (!config && VALID) {
|
|
64
|
+
({config} = SITE_SETTINGS!);
|
|
65
|
+
setConfig(config);
|
|
66
|
+
}
|
|
67
|
+
const isIPE = config && Object.values(config.functionSynonyms[0]).includes(true as unknown as string),
|
|
68
|
+
nsid = mw.config.get('wgNamespaceIds');
|
|
69
|
+
// 情形1:config已更新,可能来自localStorage
|
|
70
|
+
if (config?.img && config.redirection && config.variants && !isIPE) {
|
|
71
|
+
config.urlProtocols = config.urlProtocols.replace(/\\:/gu, ':');
|
|
72
|
+
config.tagModes = modes;
|
|
73
|
+
return {...config, nsid};
|
|
74
|
+
} else if (location.hostname.endsWith('.moegirl.org.cn')) {
|
|
75
|
+
const parserConfig: Config = await (await fetch(
|
|
76
|
+
`${CDN}/npm/wikiparser-node@browser/config/moegirl.json`,
|
|
77
|
+
)).json();
|
|
78
|
+
setObject('wikilintConfig', parserConfig);
|
|
79
|
+
config = getStaticMwConfig(parserConfig, modes);
|
|
80
|
+
} else {
|
|
81
|
+
// 以下情形均需要发送API请求
|
|
82
|
+
// 情形2:localStorage未过期但不包含新设置
|
|
83
|
+
// 情形3:新加载的 ext.CodeMirror.data
|
|
84
|
+
// 情形4:`config === null`
|
|
85
|
+
await mw.loader.using('mediawiki.api');
|
|
86
|
+
const {query: {general: {variants}, magicwords, extensiontags, functionhooks, variables}}: {
|
|
87
|
+
query: {
|
|
88
|
+
general: {variants?: {code: string}[]};
|
|
89
|
+
magicwords: MagicWord[];
|
|
90
|
+
extensiontags: string[];
|
|
91
|
+
functionhooks: string[];
|
|
92
|
+
variables: string[];
|
|
93
|
+
};
|
|
94
|
+
} = await new mw.Api().get({
|
|
95
|
+
meta: 'siteinfo',
|
|
96
|
+
siprop: [
|
|
97
|
+
'general',
|
|
98
|
+
'magicwords',
|
|
99
|
+
...config && !isIPE ? [] : ['extensiontags', 'functionhooks', 'variables'],
|
|
100
|
+
],
|
|
101
|
+
formatversion: '2',
|
|
102
|
+
}) as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
103
|
+
const others = new Set(['msg', 'raw', 'msgnw', 'subst', 'safesubst']);
|
|
104
|
+
|
|
105
|
+
// 先处理魔术字和状态开关
|
|
106
|
+
if (config && !isIPE) { // 情形2或3
|
|
107
|
+
const {functionSynonyms: [insensitive]} = config;
|
|
108
|
+
if (!('subst' in insensitive)) {
|
|
109
|
+
Object.assign(insensitive, getConfig(magicwords, ({name}) => others.has(name)));
|
|
110
|
+
}
|
|
111
|
+
} else { // 情形4:`config === null`
|
|
112
|
+
const functions = new Set([
|
|
113
|
+
...functionhooks,
|
|
114
|
+
...variables,
|
|
115
|
+
...others,
|
|
116
|
+
]);
|
|
117
|
+
// @ts-expect-error incomplete properties
|
|
118
|
+
config = {
|
|
119
|
+
tags: Object.fromEntries(extensiontags.map(tag => [tag.slice(1, -1), true])),
|
|
120
|
+
functionSynonyms: getConfigPair(magicwords, ({name}) => functions.has(name)),
|
|
121
|
+
doubleUnderscore: getConfigPair(
|
|
122
|
+
magicwords,
|
|
123
|
+
({aliases}) => aliases.some(alias => /^__.+__$/u.test(alias)),
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
config!.tagModes = modes;
|
|
128
|
+
config!.img = getConfig(magicwords, ({name}) => name.startsWith('img_'));
|
|
129
|
+
config!.variants = variants ? variants.map(({code}) => code) : [];
|
|
130
|
+
config!.redirection = magicwords.find(({name}) => name === 'redirect')!.aliases;
|
|
131
|
+
config!.urlProtocols = mw.config.get('wgUrlProtocols').replace(/\\:/gu, ':');
|
|
132
|
+
}
|
|
133
|
+
setConfig(config!);
|
|
134
|
+
ALL_SETTINGS_CACHE[SITE_ID] = {config: config!, time: Date.now()};
|
|
135
|
+
setObject('InPageEditMwConfig', ALL_SETTINGS_CACHE);
|
|
136
|
+
return {...config!, nsid};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 将MwConfig转换为Config
|
|
141
|
+
* @param minConfig 基础Config
|
|
142
|
+
* @param mwConfig
|
|
143
|
+
*/
|
|
144
|
+
export const getParserConfig = (minConfig: Config, mwConfig: MwConfig): Config => {
|
|
145
|
+
let config: Config | null = getObject('wikilintConfig');
|
|
146
|
+
if (config) {
|
|
147
|
+
return config;
|
|
148
|
+
}
|
|
149
|
+
config = {
|
|
150
|
+
...minConfig,
|
|
151
|
+
ext: Object.keys(mwConfig.tags),
|
|
152
|
+
namespaces: mw.config.get('wgFormattedNamespaces'),
|
|
153
|
+
nsid: mwConfig.nsid,
|
|
154
|
+
doubleUnderscore: mwConfig.doubleUnderscore.map(
|
|
155
|
+
obj => Object.keys(obj).map(s => s.slice(2, -2)),
|
|
156
|
+
) as [string[], string[]],
|
|
157
|
+
variants: mwConfig.variants!,
|
|
158
|
+
protocol: mwConfig.urlProtocols.replace(/\|\\?\/\\?\//u, ''),
|
|
159
|
+
redirection: mwConfig.redirection ?? minConfig.redirection,
|
|
160
|
+
};
|
|
161
|
+
if (location.hostname.endsWith('.moegirl.org.cn')) {
|
|
162
|
+
config.html[2].push('img');
|
|
163
|
+
}
|
|
164
|
+
[config.parserFunction[0]] = mwConfig.functionSynonyms;
|
|
165
|
+
if (mw.loader.getState('ext.CodeMirror') === null) {
|
|
166
|
+
for (const [key, val] of Object.entries(mwConfig.functionSynonyms[0])) {
|
|
167
|
+
if (!key.startsWith('#')) {
|
|
168
|
+
config.parserFunction[0][`#${key}`] = val;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
config.parserFunction[1] = [
|
|
173
|
+
...Object.keys(mwConfig.functionSynonyms[1]),
|
|
174
|
+
'=',
|
|
175
|
+
];
|
|
176
|
+
for (const [key, val] of Object.entries(mwConfig.img!)) {
|
|
177
|
+
config.img[key] = val.slice(4);
|
|
178
|
+
}
|
|
179
|
+
return config;
|
|
180
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bhsd/codemirror-mediawiki",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.6",
|
|
4
4
|
"description": "Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mediawiki",
|
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
"/i18n/",
|
|
17
17
|
"/dist/*.js",
|
|
18
18
|
"/dist/*.d.ts",
|
|
19
|
+
"/dist/*.mjs",
|
|
19
20
|
"!/dist/*-page.d.ts",
|
|
20
21
|
"!/dist/parser.*",
|
|
22
|
+
"/src/linter.ts",
|
|
23
|
+
"/src/static.ts",
|
|
24
|
+
"/mw/config.ts",
|
|
21
25
|
"/mediawiki.css"
|
|
22
26
|
],
|
|
23
27
|
"browser": "dist/main.min.js",
|
|
@@ -29,8 +33,8 @@
|
|
|
29
33
|
},
|
|
30
34
|
"scripts": {
|
|
31
35
|
"prepublishOnly": "npm run build",
|
|
32
|
-
"build:core": "esbuild ./src/codemirror.ts --charset=utf8 --bundle --minify --target=es2019 --format=esm --sourcemap --outfile=dist/main.min.js && tsc --emitDeclarationOnly",
|
|
33
|
-
"build:mw": "esbuild ./mw/base.ts --charset=utf8 --bundle --minify --target=es2019 --format=esm --sourcemap --outfile=dist/mw.min.js",
|
|
36
|
+
"build:core": "esbuild ./src/codemirror.ts --charset=utf8 --bundle --minify --target=es2019 --format=esm --sourcemap --outfile=dist/main.min.js && esbuild ./src/linter.ts --charset=utf8 --target=es2019 --format=esm --outfile=dist/linter.mjs && tsc --emitDeclarationOnly",
|
|
37
|
+
"build:mw": "esbuild ./mw/base.ts --charset=utf8 --bundle --minify --target=es2019 --format=esm --sourcemap --outfile=dist/mw.min.js && esbuild ./mw/config.ts --charset=utf8 --bundle --target=es2019 --format=esm --outfile=dist/mwConfig.mjs",
|
|
34
38
|
"build:wiki": "esbuild ./mw/base.ts --charset=utf8 --bundle --minify --target=es2019 --format=iife --sourcemap --outfile=dist/wiki.min.js",
|
|
35
39
|
"build:gh-page": "bash build.sh",
|
|
36
40
|
"build:test": "tsc --project test/tsconfig.json && node test/dist/test/test.js",
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
"test:real": "node test/dist/test/real.js"
|
|
46
50
|
},
|
|
47
51
|
"dependencies": {
|
|
52
|
+
"@bhsd/common": "^0.5.0",
|
|
48
53
|
"@codemirror/language": "^6.10.6",
|
|
49
54
|
"@codemirror/lint": "^6.8.4",
|
|
50
55
|
"@codemirror/state": "^6.4.1",
|
|
@@ -54,7 +59,6 @@
|
|
|
54
59
|
"wikiparser-node": "^1.14.0"
|
|
55
60
|
},
|
|
56
61
|
"devDependencies": {
|
|
57
|
-
"@bhsd/common": "^0.4.6",
|
|
58
62
|
"@codemirror/autocomplete": "^6.18.3",
|
|
59
63
|
"@codemirror/commands": "^6.7.1",
|
|
60
64
|
"@codemirror/lang-css": "^6.3.1",
|
|
@@ -66,7 +70,9 @@
|
|
|
66
70
|
"@replit/codemirror-css-color-picker": "^6.3.0",
|
|
67
71
|
"@stylistic/eslint-plugin": "^2.11.0",
|
|
68
72
|
"@stylistic/stylelint-plugin": "^3.1.1",
|
|
73
|
+
"@types/eslint": "^8.56.10",
|
|
69
74
|
"@types/jquery": "^3.5.32",
|
|
75
|
+
"@types/node": "^22.10.1",
|
|
70
76
|
"@types/oojs-ui": "^0.49.0",
|
|
71
77
|
"@typescript-eslint/eslint-plugin": "^8.16.0",
|
|
72
78
|
"@typescript-eslint/parser": "^8.16.0",
|
package/src/linter.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {CDN, loadScript} from '@bhsd/common';
|
|
2
|
+
import type {LinterBase} from 'wikiparser-node/extensions/typings';
|
|
3
|
+
import type {Linter} from 'eslint';
|
|
4
|
+
import type {Warning} from 'stylelint';
|
|
5
|
+
import type {Diagnostic} from 'luacheck-browserify';
|
|
6
|
+
|
|
7
|
+
declare type getLinter<T> = (opt?: Record<string, unknown>) => T;
|
|
8
|
+
declare type getAsyncLinter<T> = (opt?: Record<string, unknown>) => Promise<T>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 获取 WikiLint
|
|
12
|
+
* @param opt 选项
|
|
13
|
+
*/
|
|
14
|
+
export const getWikiLinter: getAsyncLinter<LinterBase> = async opt => {
|
|
15
|
+
const REPO = 'npm/wikiparser-node@browser',
|
|
16
|
+
DIR = `${REPO}/extensions/dist`,
|
|
17
|
+
lang = opt?.['i18n'];
|
|
18
|
+
await loadScript(`${DIR}/base.min.js`, 'wikiparse');
|
|
19
|
+
await loadScript(`${DIR}/lint.min.js`, 'wikiparse.Linter');
|
|
20
|
+
if (typeof lang === 'string') {
|
|
21
|
+
try {
|
|
22
|
+
const i18n: Record<string, string> =
|
|
23
|
+
await (await fetch(`${CDN}/${REPO}/i18n/${lang.toLowerCase()}.json`)).json();
|
|
24
|
+
wikiparse.setI18N(i18n);
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
return new wikiparse.Linter!(opt?.['include'] as boolean | undefined);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取 ESLint
|
|
32
|
+
* @param opt 选项
|
|
33
|
+
*/
|
|
34
|
+
export const getJsLinter: getAsyncLinter<(text: string) => Linter.LintMessage[]> = async opt => {
|
|
35
|
+
await loadScript('npm/eslint-linter-browserify@8.57.0/linter.min.js', 'eslint', true);
|
|
36
|
+
/** @see https://www.npmjs.com/package/@codemirror/lang-javascript */
|
|
37
|
+
const esLinter = new eslint.Linter(),
|
|
38
|
+
conf: Linter.Config = {
|
|
39
|
+
env: {browser: true, es2024: true},
|
|
40
|
+
parserOptions: {ecmaVersion: 15, sourceType: 'module'},
|
|
41
|
+
rules: {},
|
|
42
|
+
...opt,
|
|
43
|
+
};
|
|
44
|
+
for (const [name, {meta}] of esLinter.getRules()) {
|
|
45
|
+
if (meta?.docs?.recommended) {
|
|
46
|
+
conf.rules![name] ??= 2;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return text => esLinter.verify(text, conf);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 获取 Stylelint
|
|
54
|
+
* @param opt 选项
|
|
55
|
+
*/
|
|
56
|
+
export const getCssLinter: getAsyncLinter<(text: string) => Promise<Warning[]>> = async opt => {
|
|
57
|
+
await loadScript('npm/stylelint-bundle', 'stylelint');
|
|
58
|
+
/** @see https://www.npmjs.com/package/stylelint-config-recommended */
|
|
59
|
+
const config = {
|
|
60
|
+
rules: {
|
|
61
|
+
'annotation-no-unknown': true,
|
|
62
|
+
'at-rule-no-unknown': true,
|
|
63
|
+
'block-no-empty': true,
|
|
64
|
+
'color-no-invalid-hex': true,
|
|
65
|
+
'comment-no-empty': true,
|
|
66
|
+
'custom-property-no-missing-var-function': true,
|
|
67
|
+
'declaration-block-no-duplicate-custom-properties': true,
|
|
68
|
+
'declaration-block-no-duplicate-properties': [
|
|
69
|
+
true,
|
|
70
|
+
{
|
|
71
|
+
ignore: ['consecutive-duplicates-with-different-syntaxes'],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
'declaration-block-no-shorthand-property-overrides': true,
|
|
75
|
+
'font-family-no-duplicate-names': true,
|
|
76
|
+
'font-family-no-missing-generic-family-keyword': true,
|
|
77
|
+
'function-calc-no-unspaced-operator': true,
|
|
78
|
+
'function-linear-gradient-no-nonstandard-direction': true,
|
|
79
|
+
'function-no-unknown': true,
|
|
80
|
+
'keyframe-block-no-duplicate-selectors': true,
|
|
81
|
+
'keyframe-declaration-no-important': true,
|
|
82
|
+
'media-feature-name-no-unknown': true,
|
|
83
|
+
'media-query-no-invalid': true,
|
|
84
|
+
'named-grid-areas-no-invalid': true,
|
|
85
|
+
'no-descending-specificity': true,
|
|
86
|
+
'no-duplicate-at-import-rules': true,
|
|
87
|
+
'no-duplicate-selectors': true,
|
|
88
|
+
'no-empty-source': true,
|
|
89
|
+
'no-invalid-double-slash-comments': true,
|
|
90
|
+
'no-invalid-position-at-import-rule': true,
|
|
91
|
+
'no-irregular-whitespace': true,
|
|
92
|
+
'property-no-unknown': true,
|
|
93
|
+
'selector-anb-no-unmatchable': true,
|
|
94
|
+
'selector-pseudo-class-no-unknown': true,
|
|
95
|
+
'selector-pseudo-element-no-unknown': true,
|
|
96
|
+
'selector-type-no-unknown': [
|
|
97
|
+
true,
|
|
98
|
+
{
|
|
99
|
+
ignore: ['custom-elements'],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
'string-no-newline': true,
|
|
103
|
+
'unit-no-unknown': true,
|
|
104
|
+
...opt?.['rules'] as Record<string, unknown>,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
return async code => (await stylelint.lint({code, config})).results.flatMap(({warnings}) => warnings);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** 获取 Luacheck */
|
|
111
|
+
export const getLuaLinter: getAsyncLinter<(text: string) => Promise<Diagnostic[]>> = async () => {
|
|
112
|
+
await loadScript('npm/luacheck-browserify/dist/index.min.js', 'luacheck');
|
|
113
|
+
const luachecker = await luacheck(undefined as unknown as string);
|
|
114
|
+
return async text => (await luachecker.queue(text)).filter(({severity}) => severity);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
declare interface JsonError {
|
|
118
|
+
message: string;
|
|
119
|
+
severity: 'error';
|
|
120
|
+
line: string | undefined;
|
|
121
|
+
column: string | undefined;
|
|
122
|
+
position: string | undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** JSON.parse */
|
|
126
|
+
export const getJsonLinter: getLinter<(text: string) => JsonError[]> = () => str => {
|
|
127
|
+
try {
|
|
128
|
+
if (str.trim()) {
|
|
129
|
+
JSON.parse(str);
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
if (e instanceof SyntaxError) {
|
|
133
|
+
const {message} = e,
|
|
134
|
+
line = /\bline (\d+)/u.exec(message)?.[1],
|
|
135
|
+
column = /\bcolumn (\d+)/u.exec(message)?.[1],
|
|
136
|
+
position = /\bposition (\d+)/u.exec(message)?.[1];
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
message,
|
|
140
|
+
severity: 'error',
|
|
141
|
+
line,
|
|
142
|
+
column,
|
|
143
|
+
position,
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return [];
|
|
149
|
+
};
|
package/src/static.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type {Config} from 'wikiparser-node';
|
|
2
|
+
import type {MwConfig} from './token';
|
|
3
|
+
|
|
4
|
+
export const tagModes = {
|
|
5
|
+
onlyinclude: 'mediawiki',
|
|
6
|
+
includeonly: 'mediawiki',
|
|
7
|
+
noinclude: 'mediawiki',
|
|
8
|
+
pre: 'text/pre',
|
|
9
|
+
nowiki: 'text/nowiki',
|
|
10
|
+
indicator: 'mediawiki',
|
|
11
|
+
poem: 'mediawiki',
|
|
12
|
+
ref: 'mediawiki',
|
|
13
|
+
references: 'text/references',
|
|
14
|
+
gallery: 'text/gallery',
|
|
15
|
+
poll: 'mediawiki',
|
|
16
|
+
tabs: 'mediawiki',
|
|
17
|
+
tab: 'mediawiki',
|
|
18
|
+
choose: 'text/choose',
|
|
19
|
+
option: 'mediawiki',
|
|
20
|
+
combobox: 'text/combobox',
|
|
21
|
+
combooption: 'mediawiki',
|
|
22
|
+
inputbox: 'text/inputbox',
|
|
23
|
+
templatedata: 'json',
|
|
24
|
+
mapframe: 'json',
|
|
25
|
+
maplink: 'json',
|
|
26
|
+
graph: 'json',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Object.fromEntries polyfill
|
|
31
|
+
* @param entries
|
|
32
|
+
* @param obj
|
|
33
|
+
* @param string 是否为字符串
|
|
34
|
+
*/
|
|
35
|
+
const fromEntries = (entries: readonly string[], obj: Record<string, unknown>, string?: boolean): void => {
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
obj[entry] = string ? entry : true;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const getStaticMwConfig = (
|
|
42
|
+
{parserFunction, protocol, nsid, variants, redirection, ext, doubleUnderscore, img}: Config,
|
|
43
|
+
modes: Record<string, string>,
|
|
44
|
+
): MwConfig => {
|
|
45
|
+
const mwConfig: MwConfig = {
|
|
46
|
+
tags: {},
|
|
47
|
+
tagModes: modes,
|
|
48
|
+
doubleUnderscore: [{}, {}],
|
|
49
|
+
functionSynonyms: [parserFunction[0], {}],
|
|
50
|
+
urlProtocols: `${protocol}|//`,
|
|
51
|
+
nsid,
|
|
52
|
+
img: Object.fromEntries(Object.entries(img).map(([key, val]) => [key, `img_${val}`])),
|
|
53
|
+
variants,
|
|
54
|
+
redirection,
|
|
55
|
+
},
|
|
56
|
+
[insensitive,, obj] = doubleUnderscore;
|
|
57
|
+
fromEntries(ext, mwConfig.tags);
|
|
58
|
+
fromEntries(
|
|
59
|
+
(obj && insensitive.length === 0 ? Object.keys(obj) : insensitive).map(s => `__${s}__`),
|
|
60
|
+
mwConfig.doubleUnderscore[0],
|
|
61
|
+
);
|
|
62
|
+
fromEntries(doubleUnderscore[1].map(s => `__${s}__`), mwConfig.doubleUnderscore[1]);
|
|
63
|
+
fromEntries((parserFunction.slice(2) as string[][]).flat(), mwConfig.functionSynonyms[0], true);
|
|
64
|
+
fromEntries(parserFunction[1], mwConfig.functionSynonyms[1]);
|
|
65
|
+
return mwConfig;
|
|
66
|
+
};
|