@bhsd/codemirror-mediawiki 2.1.7 → 2.1.8

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/mw/dist/base.js CHANGED
@@ -1,8 +1,9 @@
1
- import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.7/dist/main.min.js';
1
+ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.8/dist/main.min.js';
2
2
  (() => {
3
3
  var _a;
4
- mw.loader.load('https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.7/mediawiki.min.css', 'text/css');
4
+ mw.loader.load('https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.8/mediawiki.min.css', 'text/css');
5
5
  const instances = new WeakMap();
6
+ const getInstance = ($ele) => instances.get($ele[0]);
6
7
  $.valHooks['textarea'] = {
7
8
  get(elem) {
8
9
  const cm = instances.get(elem);
@@ -11,9 +12,7 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
11
12
  set(elem, value) {
12
13
  const cm = instances.get(elem);
13
14
  if (cm === null || cm === void 0 ? void 0 : cm.visible) {
14
- cm.view.dispatch({
15
- changes: { from: 0, to: cm.view.state.doc.length, insert: value },
16
- });
15
+ cm.setContent(value);
17
16
  }
18
17
  else {
19
18
  elem.value = value;
@@ -21,26 +20,23 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
21
20
  },
22
21
  };
23
22
  function getCaretPosition(option) {
24
- const { view: { state: { selection: { main } } } } = instances.get(this[0]);
23
+ const { view: { state: { selection: { main } } } } = getInstance(this);
25
24
  return (option === null || option === void 0 ? void 0 : option.startAndEnd) ? [main.from, main.to] : main.head;
26
25
  }
27
26
  const textSelection = {
28
27
  getContents() {
29
- return instances.get(this[0]).view.state.doc.toString();
28
+ return getInstance(this).view.state.doc.toString();
30
29
  },
31
30
  setContents(content) {
32
- const { view } = instances.get(this[0]);
33
- view.dispatch({
34
- changes: { from: 0, to: view.state.doc.length, insert: content },
35
- });
31
+ getInstance(this).setContent(content);
36
32
  return this;
37
33
  },
38
34
  getSelection() {
39
- const { view: { state } } = instances.get(this[0]);
35
+ const { view: { state } } = getInstance(this);
40
36
  return state.sliceDoc(state.selection.main.from, state.selection.main.to);
41
37
  },
42
38
  setSelection({ start, end }) {
43
- const { view } = instances.get(this[0]);
39
+ const { view } = getInstance(this);
44
40
  view.dispatch({
45
41
  selection: { anchor: start, head: end !== null && end !== void 0 ? end : start },
46
42
  });
@@ -48,36 +44,44 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
48
44
  return this;
49
45
  },
50
46
  replaceSelection(value) {
51
- const { view } = instances.get(this[0]);
47
+ const { view } = getInstance(this);
52
48
  view.dispatch(view.state.replaceSelection(value));
53
49
  return this;
54
50
  },
55
51
  getCaretPosition,
56
52
  scrollToCaretPosition() {
57
- instances.get(this[0]).view.dispatch({ scrollIntoView: true });
53
+ getInstance(this).view.dispatch({ scrollIntoView: true });
58
54
  return this;
59
55
  },
60
56
  };
61
- const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null, DATA_MODULE = mw.loader.getState('ext.CodeMirror.data') ? 'ext.CodeMirror.data' : 'ext.CodeMirror', ALL_SETTINGS_CACHE = (_a = JSON.parse(localStorage.getItem('InPageEditMwConfig'))) !== null && _a !== void 0 ? _a : {}, SITE_ID = `${mw.config.get('wgServerName')}${mw.config.get('wgScriptPath')}`, SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID], EXPIRED = !(SITE_SETTINGS && SITE_SETTINGS.time > Date.now() - 86400 * 1000 * 30);
62
- const getAliases = (words) => words.flatMap(({ aliases, name }) => aliases.map(alias => ({ alias, name })));
63
- const getConfig = (aliases) => {
64
- const config = {};
65
- for (const { alias, name } of aliases) {
66
- config[alias.replace(/:$/u, '')] = name;
57
+ const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null, DATA_MODULE = mw.loader.getState('ext.CodeMirror.data') ? 'ext.CodeMirror.data' : 'ext.CodeMirror', ALL_SETTINGS_CACHE = (_a = JSON.parse(localStorage.getItem('InPageEditMwConfig'))) !== null && _a !== void 0 ? _a : {}, SITE_ID = `${mw.config.get('wgServerName')}${mw.config.get('wgScriptPath')}`, SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID], VALID = (SITE_SETTINGS === null || SITE_SETTINGS === void 0 ? void 0 : SITE_SETTINGS.time) > Date.now() - 86400 * 1000 * 30;
58
+ const getConfig = (magicWords, rule, flip) => {
59
+ const words = magicWords.filter(rule).filter(({ 'case-sensitive': i }) => i !== flip)
60
+ .flatMap(({ aliases, name, 'case-sensitive': i }) => aliases.map(alias => ({
61
+ alias: (i ? alias : alias.toLowerCase()).replace(/:$/u, ''),
62
+ name,
63
+ }))), obj = {};
64
+ for (const { alias, name } of words) {
65
+ obj[alias] = name;
67
66
  }
68
- return config;
67
+ return obj;
68
+ };
69
+ const getConfigPair = (magicWords, rule) => [true, false]
70
+ .map(bool => getConfig(magicWords, rule, bool));
71
+ const setConfig = (config) => {
72
+ mw.config.set('extCodeMirrorConfig', config);
69
73
  };
70
74
  const getMwConfig = async () => {
71
- if (USING_LOCAL && EXPIRED) {
75
+ if (USING_LOCAL && !VALID) {
72
76
  await mw.loader.using(DATA_MODULE);
73
77
  }
74
78
  let config = mw.config.get('extCodeMirrorConfig');
75
- if (!config && !EXPIRED) {
79
+ if (!config && VALID) {
76
80
  ({ config } = SITE_SETTINGS);
77
- mw.config.set('extCodeMirrorConfig', config);
81
+ setConfig(config);
78
82
  }
79
83
  const isIPE = config && Object.values(config.functionSynonyms[0]).includes(true);
80
- if (config && config.img && config.variants && !isIPE) {
84
+ if ((config === null || config === void 0 ? void 0 : config.img) && config.variants && !isIPE) {
81
85
  return {
82
86
  ...config,
83
87
  nsid: mw.config.get('wgNamespaceIds'),
@@ -92,20 +96,25 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
92
96
  ],
93
97
  formatversion: '2',
94
98
  });
95
- const otherMagicwords = new Set(['msg', 'raw', 'msgnw', 'subst', 'safesubst']);
99
+ const others = new Set(['msg', 'raw', 'msgnw', 'subst', 'safesubst']);
96
100
  if (config && !isIPE) {
97
101
  const { functionSynonyms: [insensitive] } = config;
98
102
  if (!('subst' in insensitive)) {
99
- const aliases = getAliases(magicwords.filter(({ name }) => otherMagicwords.has(name)));
100
- for (const { alias, name } of aliases) {
101
- insensitive[alias.replace(/:$/u, '')] = name;
102
- }
103
+ Object.assign(insensitive, getConfig(magicwords, ({ name }) => others.has(name)));
103
104
  }
104
105
  }
105
106
  else {
106
107
  config = {
107
108
  tagModes: {
109
+ tab: 'text/mediawiki',
110
+ indicator: 'text/mediawiki',
111
+ poem: 'text/mediawiki',
108
112
  ref: 'text/mediawiki',
113
+ option: 'text/mediawiki',
114
+ combooption: 'text/mediawiki',
115
+ tabs: 'text/mediawiki',
116
+ poll: 'text/mediawiki',
117
+ gallery: 'text/mediawiki',
109
118
  },
110
119
  tags: {},
111
120
  urlProtocols: mw.config.get('wgUrlProtocols'),
@@ -113,24 +122,18 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
113
122
  for (const tag of extensiontags) {
114
123
  config.tags[tag.slice(1, -1)] = true;
115
124
  }
116
- const realMagicwords = new Set([
125
+ const functions = new Set([
117
126
  ...functionhooks,
118
127
  ...variables,
119
- ...otherMagicwords,
120
- ]), allMagicwords = magicwords.filter(({ name, aliases }) => aliases.some(alias => /^__.+__$/u.test(alias)) || realMagicwords.has(name)), sensitive = getAliases(allMagicwords.filter(word => word['case-sensitive'])), insensitive = getAliases(allMagicwords.filter(word => !word['case-sensitive'])).map(({ alias, name }) => ({ alias: alias.toLowerCase(), name }));
121
- config.doubleUnderscore = [
122
- getConfig(insensitive.filter(({ alias }) => /^__.+__$/u.test(alias))),
123
- getConfig(sensitive.filter(({ alias }) => /^__.+__$/u.test(alias))),
124
- ];
125
- config.functionSynonyms = [
126
- getConfig(insensitive.filter(({ alias }) => !/^__.+__|^#$/u.test(alias))),
127
- getConfig(sensitive.filter(({ alias }) => !/^__.+__|^#$/u.test(alias))),
128
- ];
128
+ ...others,
129
+ ]);
130
+ config.functionSynonyms = getConfigPair(magicwords, ({ name }) => functions.has(name));
131
+ config.doubleUnderscore = getConfigPair(magicwords, ({ aliases }) => aliases.some(alias => /^__.+__$/u.test(alias)));
129
132
  }
130
- config.img = getConfig(getAliases(magicwords.filter(({ name }) => name.startsWith('img_'))));
133
+ config.img = getConfig(magicwords, ({ name }) => name.startsWith('img_'));
131
134
  config.variants = variants ? variants.map(({ code }) => code) : [];
132
135
  config.nsid = mw.config.get('wgNamespaceIds');
133
- mw.config.set('extCodeMirrorConfig', config);
136
+ setConfig(config);
134
137
  ALL_SETTINGS_CACHE[SITE_ID] = { config: config, time: Date.now() };
135
138
  localStorage.setItem('InPageEditMwConfig', JSON.stringify(ALL_SETTINGS_CACHE));
136
139
  return config;
@@ -149,6 +152,11 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
149
152
  super.toggle(show);
150
153
  $(this.textarea).data('jquery.textSelection', show && textSelection);
151
154
  }
155
+ async getLinter(opt) {
156
+ const linter = await super.getLinter(opt);
157
+ linters[this.lang] = linter;
158
+ return linter;
159
+ }
152
160
  async defaultLint(on, opt) {
153
161
  if (!on) {
154
162
  this.lint();
@@ -156,13 +164,13 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
156
164
  }
157
165
  const { lang } = this;
158
166
  if (!(lang in linters)) {
159
- linters[lang] = await this.getLinter(opt);
160
- if (this.lang === 'mediawiki') {
167
+ await this.getLinter(opt);
168
+ if (lang === 'mediawiki') {
161
169
  const mwConfig = await getMwConfig(), config = {
162
170
  ...await wikiparse.getConfig(),
163
171
  ext: Object.keys(mwConfig.tags),
164
172
  namespaces: mw.config.get('wgFormattedNamespaces'),
165
- nsid: mw.config.get('wgNamespaceIds'),
173
+ nsid: mwConfig.nsid,
166
174
  doubleUnderscore: mwConfig.doubleUnderscore.map(obj => Object.keys(obj).map(s => s.slice(2, -2))),
167
175
  variants: mwConfig.variants,
168
176
  protocol: mwConfig.urlProtocols.replace(/\\:/gu, ':'),
@@ -185,6 +193,9 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
185
193
  wikiparse.setConfig(config);
186
194
  }
187
195
  }
196
+ else if (opt) {
197
+ await this.getLinter(opt);
198
+ }
188
199
  if (linters[lang]) {
189
200
  this.lint(linters[lang]);
190
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhsd/codemirror-mediawiki",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror",
5
5
  "keywords": [
6
6
  "mediawiki",
package/src/codemirror.ts CHANGED
@@ -70,6 +70,14 @@ const loadScript = (src: string, target: string): Promise<void> => new Promise(r
70
70
  document.head.append(script);
71
71
  });
72
72
 
73
+ /**
74
+ * 获取指定行列的位置
75
+ * @param doc 文档
76
+ * @param line 行号
77
+ * @param column 列号
78
+ */
79
+ const pos = (doc: Text, line: number, column: number): number => doc.line(line).from + column - 1;
80
+
73
81
  export class CodeMirror6 {
74
82
  readonly #textarea;
75
83
  readonly #language;
@@ -143,12 +151,12 @@ export class CodeMirror6 {
143
151
  extensions,
144
152
  doc: textarea.value,
145
153
  });
146
- const {offsetHeight, selectionStart, selectionEnd, scrollTop} = textarea,
154
+ const {selectionStart, selectionEnd, scrollTop} = textarea,
147
155
  {fontSize, lineHeight} = getComputedStyle(textarea),
148
156
  hasFocus = document.activeElement === textarea;
149
157
  textarea.parentNode!.insertBefore(this.#view.dom, textarea);
150
- this.#view.dom.style.minHeight = '2em';
151
- this.#view.dom.style.height = `${offsetHeight}px`;
158
+ this.#minHeight();
159
+ this.#refresh();
152
160
  this.#view.dom.style.fontSize = fontSize;
153
161
  this.#view.scrollDOM.style.lineHeight = lineHeight;
154
162
  this.#view.requestMeasure();
@@ -164,6 +172,20 @@ export class CodeMirror6 {
164
172
  });
165
173
  }
166
174
 
175
+ /** 刷新编辑器高度 */
176
+ #refresh(): void {
177
+ const {offsetHeight} = this.#textarea;
178
+ this.#view.dom.style.height = offsetHeight ? `${offsetHeight}px` : this.#textarea.style.height;
179
+ }
180
+
181
+ /**
182
+ * 设置编辑器最小高度
183
+ * @param linting 是否启用语法检查
184
+ */
185
+ #minHeight(linting?: boolean): void {
186
+ this.#view.dom.style.minHeight = linting ? 'calc(100px + 2em)' : '2em';
187
+ }
188
+
167
189
  /**
168
190
  * 设置语言
169
191
  * @param lang 语言
@@ -193,8 +215,10 @@ export class CodeMirror6 {
193
215
  : [];
194
216
  if (lintSource) {
195
217
  linters[this.#lang] = linterExtension;
218
+ this.#minHeight(true);
196
219
  } else {
197
220
  delete linters[this.#lang];
221
+ this.#minHeight();
198
222
  }
199
223
  this.#view.dispatch({
200
224
  effects: [this.#linter.reconfigure(linterExtension)],
@@ -244,8 +268,8 @@ export class CodeMirror6 {
244
268
  async getLinter(opt?: Record<string, unknown>): Promise<LintSource | undefined> {
245
269
  switch (this.#lang) {
246
270
  case 'mediawiki': {
247
- const src = 'combine/npm/wikiparser-node@1.3.2-b/extensions/dist/base.min.js,'
248
- + 'npm/wikiparser-node@1.3.2-b/extensions/dist/lint.min.js';
271
+ const CDN = 'npm/wikiparser-node@1.3.4-b/extensions/dist',
272
+ src = `combine/${CDN}/base.min.js,${CDN}/lint.min.js`;
249
273
  await loadScript(src, 'wikiparse');
250
274
  const wikiLinter = new wikiparse.Linter(opt?.['include'] as boolean);
251
275
  return doc => wikiLinter.codemirror(doc.toString());
@@ -267,18 +291,18 @@ export class CodeMirror6 {
267
291
  ...opt,
268
292
  };
269
293
  for (const [name, {meta}] of esLinter.getRules()) {
270
- if (meta?.docs!.recommended) {
294
+ if (meta?.docs?.recommended) {
271
295
  conf.rules![name] ??= 2;
272
296
  }
273
297
  }
274
298
  return doc => esLinter.verify(doc.toString(), conf)
275
299
  .map(({message, severity, line, column, endLine, endColumn}) => {
276
- const from = doc.line(line).from + column - 1;
300
+ const from = pos(doc, line, column);
277
301
  return {
278
302
  message,
279
303
  severity: severity === 1 ? 'warning' : 'error',
280
304
  from,
281
- to: endLine === undefined ? from + 1 : doc.line(endLine).from + endColumn! - 1,
305
+ to: endLine === undefined ? from + 1 : pos(doc, endLine, endColumn!),
282
306
  };
283
307
  });
284
308
  }
@@ -339,8 +363,8 @@ export class CodeMirror6 {
339
363
  .map(({text, severity, line, column, endLine, endColumn}) => ({
340
364
  message: text,
341
365
  severity,
342
- from: doc.line(line).from + column - 1,
343
- to: endLine === undefined ? doc.line(line).to : doc.line(endLine).from + endColumn! - 1,
366
+ from: pos(doc, line, column),
367
+ to: endLine === undefined ? doc.line(line).to : pos(doc, endLine, endColumn!),
344
368
  }));
345
369
  };
346
370
  }
@@ -369,15 +393,24 @@ export class CodeMirror6 {
369
393
  }
370
394
  }
371
395
 
396
+ /**
397
+ * 重设编辑器内容
398
+ * @param content 新内容
399
+ */
400
+ setContent(content: string): void {
401
+ this.#view.dispatch({
402
+ changes: {from: 0, to: this.#view.state.doc.length, insert: content},
403
+ });
404
+ }
405
+
372
406
  /**
373
407
  * 在编辑器和文本框之间切换
374
408
  * @param show 是否显示编辑器
375
409
  */
376
410
  toggle(show = !this.#visible): void {
377
411
  if (show && !this.#visible) {
378
- this.#view.dispatch({
379
- changes: {from: 0, to: this.#view.state.doc.length, insert: this.#textarea.value},
380
- });
412
+ this.setContent(this.#textarea.value);
413
+ this.#refresh();
381
414
  }
382
415
  this.#visible = show;
383
416
  this.#view.dom.style.setProperty('display', show ? '' : 'none', 'important');