wikilint 2.28.1 → 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.
Files changed (51) hide show
  1. package/data/ext/ThirdPartyNotices.txt +33 -0
  2. package/data/ext/mapframe.json +489 -2
  3. package/dist/base.d.mts +4 -2
  4. package/dist/base.d.ts +4 -2
  5. package/dist/base.js +1 -0
  6. package/dist/base.mjs +2 -1
  7. package/dist/bin/config.js +1 -1
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.js +23 -2
  10. package/dist/lib/document.d.ts +23 -7
  11. package/dist/lib/document.js +7 -27
  12. package/dist/lib/element.js +1 -1
  13. package/dist/lib/lintConfig.js +2 -0
  14. package/dist/lib/lsp.d.ts +1 -12
  15. package/dist/lib/lsp.js +41 -75
  16. package/dist/lib/node.js +23 -20
  17. package/dist/lib/title.d.ts +3 -1
  18. package/dist/lib/title.js +37 -9
  19. package/dist/mixin/elementLike.js +14 -9
  20. package/dist/parser/commentAndExt.js +30 -26
  21. package/dist/parser/links.js +4 -3
  22. package/dist/parser/redirect.js +1 -1
  23. package/dist/parser/selector.js +6 -8
  24. package/dist/src/arg.js +2 -2
  25. package/dist/src/attribute.js +27 -0
  26. package/dist/src/attributes.js +1 -1
  27. package/dist/src/converter.js +6 -3
  28. package/dist/src/imageParameter.d.ts +3 -1
  29. package/dist/src/imageParameter.js +18 -3
  30. package/dist/src/index.d.ts +8 -0
  31. package/dist/src/index.js +21 -28
  32. package/dist/src/link/file.js +7 -7
  33. package/dist/src/link/galleryImage.js +1 -1
  34. package/dist/src/link/redirectTarget.js +1 -1
  35. package/dist/src/multiLine/gallery.js +2 -2
  36. package/dist/src/multiLine/imagemap.js +3 -4
  37. package/dist/src/multiLine/paramTag.js +2 -2
  38. package/dist/src/nowiki/index.js +59 -2
  39. package/dist/src/table/base.js +1 -2
  40. package/dist/src/table/index.js +1 -2
  41. package/dist/src/transclude.js +3 -3
  42. package/dist/util/constants.js +3 -1
  43. package/dist/util/debug.js +1 -1
  44. package/dist/util/search.js +16 -0
  45. package/dist/util/sharable.js +27 -3
  46. package/dist/util/sharable.mjs +28 -4
  47. package/i18n/en.json +2 -0
  48. package/i18n/zh-hans.json +2 -0
  49. package/i18n/zh-hant.json +2 -0
  50. package/package.json +5 -3
  51. package/data/ext/maplink.json +0 -4
@@ -3,14 +3,30 @@ import type { TextDocument } from 'vscode-languageserver-textdocument';
3
3
  import type { JSONDocument } from 'vscode-json-languageservice';
4
4
  import type { Stylesheet } from 'vscode-css-languageservice';
5
5
  import type { Token } from '../internal';
6
- declare interface Jax {
7
- tex2mml(tex: string): string;
6
+ export interface TexvcLocation {
7
+ offset: number;
8
+ line: number;
9
+ column: number;
8
10
  }
9
- /**
10
- * Load MathJax
11
- * @param id MathJax module ID
12
- */
13
- export declare const loadMathJax: (id?: string) => Promise<Jax> | undefined;
11
+ declare interface Texvcjs {
12
+ check(input: string, options?: {
13
+ usemhchem?: boolean;
14
+ }): {
15
+ status: '+';
16
+ } | {
17
+ status: 'C';
18
+ } | {
19
+ status: 'F' | 'S';
20
+ error: {
21
+ message: string;
22
+ location: {
23
+ start: TexvcLocation;
24
+ end: TexvcLocation;
25
+ };
26
+ };
27
+ };
28
+ }
29
+ export declare const texvcjs: Texvcjs | undefined;
14
30
  export declare const jsonTags: string[];
15
31
  export declare const jsonLSP: import("vscode-json-languageservice").LanguageService | undefined;
16
32
  export declare const cssLSP: import("vscode-css-languageservice").LanguageService | undefined;
@@ -3,38 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.EmbeddedCSSDocument = exports.EmbeddedJSONDocument = exports.stylelint = exports.htmlData = exports.cssLSP = exports.jsonLSP = exports.jsonTags = exports.loadMathJax = void 0;
6
+ exports.EmbeddedCSSDocument = exports.EmbeddedJSONDocument = exports.stylelint = exports.htmlData = exports.cssLSP = exports.jsonLSP = exports.jsonTags = exports.texvcjs = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const common_1 = require("@bhsd/common");
9
- let MathJax;
10
- /**
11
- * Load MathJax
12
- * @param id MathJax module ID
13
- */
14
- const loadMathJax = (id = 'mathjax') => {
9
+ exports.texvcjs = (() => {
15
10
  try {
16
- const jax = require(id);
17
- MathJax ??= jax.init({
18
- loader: {
19
- load: ['input/tex', '[tex]/mhchem'],
20
- },
21
- tex: {
22
- packages: { '[+]': ['mhchem'] },
23
- /** @ignore */
24
- formatError(_, error) {
25
- throw error;
26
- },
27
- },
28
- startup: { typeset: false },
29
- });
30
- return MathJax;
11
+ return require('mathoid-texvcjs');
31
12
  }
32
13
  catch {
33
14
  /* istanbul ignore next */
34
15
  return undefined;
35
16
  }
36
- };
37
- exports.loadMathJax = loadMathJax;
17
+ })();
38
18
  exports.jsonTags = ['templatedata', 'mapframe', 'maplink'];
39
19
  exports.jsonLSP = (() => {
40
20
  try {
@@ -44,12 +24,12 @@ exports.jsonLSP = (() => {
44
24
  async schemaRequestService(uri) {
45
25
  return (await fetch(uri)).text();
46
26
  },
47
- });
27
+ }), dir = path_1.default.join('..', '..', 'data', 'ext');
48
28
  lsp.configure({
49
29
  schemas: exports.jsonTags.map((tag) => {
50
- const uri = path_1.default.join('..', '..', 'data', 'ext', tag);
30
+ const uri = path_1.default.join(dir, tag);
51
31
  try {
52
- const schema = require(uri);
32
+ const schema = require(tag === 'maplink' ? path_1.default.join(dir, 'mapframe') : uri);
53
33
  return {
54
34
  uri,
55
35
  fileMatch: [tag],
@@ -250,7 +250,7 @@ let AstElement = (() => {
250
250
  child.setAttribute('aIndex', cur);
251
251
  const childErrors = child.lint(cur, re);
252
252
  if (childErrors.length > 0) {
253
- errors.push(...childErrors);
253
+ Array.prototype.push.apply(errors, childErrors);
254
254
  }
255
255
  cur += child.toString().length + this.getGaps(i);
256
256
  }
@@ -52,6 +52,7 @@ const defaultLintRuleConfig = {
52
52
  // extension: 2,
53
53
  // image: 2,
54
54
  parameter: 1,
55
+ // thumb: 2,
55
56
  },
56
57
  ],
57
58
  'invalid-imagemap': [
@@ -217,6 +218,7 @@ const defaultLintRuleConfig = {
217
218
  warn: 1,
218
219
  },
219
220
  ],
221
+ 'invalid-math': 2,
220
222
  };
221
223
  Object.freeze(defaultLintRuleConfig);
222
224
  const defaultLintConfig = {
package/dist/lib/lsp.d.ts CHANGED
@@ -1,28 +1,17 @@
1
1
  import Parser from '../index';
2
2
  import type { Range, Position, ColorInformation, ColorPresentation, FoldingRange, DocumentLink, Location, WorkspaceEdit, Diagnostic as DiagnosticBase, TextEdit, Hover, SignatureHelp, InlayHint, CodeAction, DocumentSymbol } from 'vscode-languageserver-types';
3
3
  import type { Config, LanguageService as LanguageServiceBase, CompletionItem, SignatureData } from '../base';
4
- import type { Token, AttributeToken } from '../internal';
4
+ import type { AttributeToken } from '../internal';
5
5
  export interface QuickFixData extends TextEdit {
6
6
  title: string;
7
7
  fix: boolean;
8
8
  }
9
9
  export declare const tasks: WeakMap<object, Parser.LanguageService>;
10
- /**
11
- * Check if a token is a plain attribute.
12
- * @param token
13
- * @param token.type
14
- * @param token.parentNode
15
- * @param token.length
16
- * @param token.firstChild
17
- * @param style whether it is a style attribute
18
- */
19
- export declare const isAttr: ({ type, parentNode, length, firstChild }: Token, style?: boolean) => boolean | undefined;
20
10
  /** VSCode-style language service */
21
11
  export declare class LanguageService implements LanguageServiceBase {
22
12
  #private;
23
13
  /** @since v1.17.1 */
24
14
  include: boolean;
25
- lilypond: string;
26
15
  /** @param uri 任务标识 */
27
16
  constructor(uri: object);
28
17
  /** @implements */
package/dist/lib/lsp.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.LanguageService = exports.isAttr = exports.tasks = void 0;
6
+ exports.LanguageService = exports.tasks = void 0;
7
7
  const common_1 = require("@bhsd/common");
8
8
  const cm_util_1 = require("@bhsd/cm-util");
9
9
  const base_1 = require("../base");
@@ -18,9 +18,11 @@ const util_1 = __importDefault(require("util"));
18
18
  const child_process_1 = require("child_process");
19
19
  const crypto_1 = require("crypto");
20
20
  const stylelint_util_1 = require("@bhsd/stylelint-util");
21
+ const search_1 = __importDefault(require("../util/search"));
22
+ const constants_1 = require("../util/constants");
21
23
  const document_1 = require("./document");
22
24
  /** @see https://www.npmjs.com/package/stylelint-config-recommended */
23
- const cssRules = { 'block-no-empty': null }, jsonSelector = document_1.jsonTags.map(s => `ext#${s}`).join(), mathTags = ['math', 'chem', 'ce'], mathSelector = mathTags.map(s => `ext#${s}`).join(), scores = new Map();
25
+ const cssRules = { 'block-no-empty': null }, sources = { 'invalid-css': 'css', 'invalid-math': 'texvc' }, jsonSelector = document_1.jsonTags.map(s => `ext#${s}`).join(), scores = new Map();
24
26
  let colors;
25
27
  /* NOT FOR BROWSER ONLY END */
26
28
  exports.tasks = new WeakMap();
@@ -53,7 +55,6 @@ const isAttr = ({ type, parentNode, length, firstChild }, style) => type === 'at
53
55
  && (!style
54
56
  || parentNode.name === 'style'
55
57
  && Boolean(document_1.cssLSP));
56
- exports.isAttr = isAttr;
57
58
  /**
58
59
  * Check if a token is an HTML attribute.
59
60
  * @param token
@@ -311,10 +312,11 @@ class LanguageService {
311
312
  config;
312
313
  /** @private */
313
314
  data;
315
+ /* NOT FOR BROWSER ONLY */
316
+ /** @private */
314
317
  lilypond;
315
318
  #lilypondData;
316
319
  #mathData;
317
- #mathSet;
318
320
  /* NOT FOR BROWSER ONLY END */
319
321
  /** @param uri 任务标识 */
320
322
  constructor(uri) {
@@ -323,7 +325,6 @@ class LanguageService {
323
325
  const dataDir = path_1.default.join('..', '..', 'data'), extDir = path_1.default.join(dataDir, 'ext');
324
326
  this.#lilypondData = require(path_1.default.join(extDir, 'score'));
325
327
  this.#mathData = require(path_1.default.join(extDir, 'math'));
326
- this.#mathSet = new Set(this.#mathData);
327
328
  /* NOT FOR BROWSER ONLY END */
328
329
  Object.defineProperties(this, {
329
330
  config: { enumerable: false },
@@ -451,8 +452,7 @@ class LanguageService {
451
452
  })();
452
453
  const re = await colors;
453
454
  /* NOT FOR BROWSER ONLY END */
454
- return root.querySelectorAll('attr-value,parameter-value,arg-default').reverse()
455
- .flatMap(token => {
455
+ return root.querySelectorAll('attr-value,parameter-value,arg-default').reverse().flatMap(token => {
456
456
  const { type, childNodes,
457
457
  /* NOT FOR BROWSER ONLY */
458
458
  parentNode, } = token;
@@ -460,7 +460,7 @@ class LanguageService {
460
460
  return [];
461
461
  /* NOT FOR BROWSER ONLY */
462
462
  }
463
- else if ((0, exports.isAttr)(token, true)) {
463
+ else if (isAttr(token, true)) {
464
464
  const textDoc = new document_1.EmbeddedCSSDocument(root, token);
465
465
  return document_1.cssLSP.findDocumentColors(textDoc, textDoc.styleSheet);
466
466
  /* NOT FOR BROWSER ONLY END */
@@ -468,8 +468,7 @@ class LanguageService {
468
468
  /* NOT FOR BROWSER ONLY */
469
469
  const isStyle = re && type === 'attr-value' && parentNode.name === 'style';
470
470
  /* NOT FOR BROWSER ONLY END */
471
- return childNodes.filter((child) => child.type === 'text').reverse()
472
- .flatMap(child => {
471
+ return childNodes.filter((child) => child.type === 'text').reverse().flatMap(child => {
473
472
  const { data } = child, parts = (0, common_1.splitColors)(data, hsl).filter(([, , , isColor]) => isColor);
474
473
  /* NOT FOR BROWSER ONLY */
475
474
  if (isStyle) {
@@ -697,7 +696,7 @@ class LanguageService {
697
696
  : undefined;
698
697
  /* NOT FOR BROWSER ONLY */
699
698
  }
700
- else if ((0, exports.isAttr)(cur, true)) {
699
+ else if (isAttr(cur, true)) {
701
700
  const textDoc = new document_1.EmbeddedCSSDocument(root, cur);
702
701
  return document_1.cssLSP.doComplete(textDoc, position, textDoc.styleSheet).items.map((item) => ({
703
702
  ...item,
@@ -732,7 +731,7 @@ class LanguageService {
732
731
  ];
733
732
  }
734
733
  }
735
- else if (type === 'ext-inner' && mathTags.includes(cur.name)) {
734
+ else if (type === 'ext-inner' && constants_1.mathTags.has(cur.name)) {
736
735
  const word = /(?<!\\)\\[a-z]+$/iu.exec(curLine.slice(0, character))?.[0];
737
736
  if (word) {
738
737
  const data = this.#mathData;
@@ -742,7 +741,7 @@ class LanguageService {
742
741
  }
743
742
  /* NOT FOR BROWSER ONLY END */
744
743
  }
745
- else if ((0, exports.isAttr)(cur) && isHtmlAttr(parentNode)) {
744
+ else if (isAttr(cur) && isHtmlAttr(parentNode)) {
746
745
  const data = (0, lint_1.provideValues)(parentNode.tag, parentNode.name);
747
746
  if (data.length === 0) {
748
747
  return undefined;
@@ -774,8 +773,7 @@ class LanguageService {
774
773
  severity: severity === 'error' ? 1 : 2,
775
774
  source:
776
775
  /* eslint-disable @stylistic/operator-linebreak */
777
- rule === 'invalid-css' ?
778
- 'css' :
776
+ sources[rule] ??
779
777
  'WikiLint',
780
778
  code: code ??
781
779
  /* eslint-enable @stylistic/operator-linebreak */
@@ -804,7 +802,7 @@ class LanguageService {
804
802
  return acc;
805
803
  });
806
804
  return cssErrors.map(({ rule, text: msg, severity, line, column, endLine = line, endColumn = column, fix, }) => {
807
- const i = bottoms.findIndex(bottom => bottom >= line);
805
+ const i = (0, search_1.default)(bottoms, line, (bottom, needle) => bottom - needle);
808
806
  return {
809
807
  range: {
810
808
  start: getStylelintPos(rects[i], bottoms[i], line, column - 1),
@@ -892,42 +890,6 @@ class LanguageService {
892
890
  }));
893
891
  }
894
892
  }
895
- const MathJax = await (0, document_1.loadMathJax)(this.mathjax), data = this.#mathSet, mathDiagnostics = root.querySelectorAll(mathSelector)
896
- .map(token => {
897
- const { selfClosing, innerText, lastChild, name } = token;
898
- if (selfClosing) {
899
- return [];
900
- }
901
- const hasCe = name === 'math' && token.hasAttr('chem'), mathErrors = [...innerText.matchAll(/(?<!\\)\\[a-z]+/giu)]
902
- .filter(([macro]) => !(hasCe && macro === String.raw `\ce` || data.has(macro)))
903
- .map(({ 0: macro, index }) => {
904
- const aIndex = lastChild.getAbsoluteIndex() + index;
905
- return {
906
- range: createRange(root, aIndex, aIndex + macro.length),
907
- severity: 2,
908
- source: 'MathJax',
909
- code: 'UnknownMacro',
910
- message: `Unknown macro "${macro}"`,
911
- };
912
- });
913
- if (MathJax) {
914
- try {
915
- MathJax.tex2mml(name === 'math' ? innerText : String.raw `\ce{${innerText}}`);
916
- }
917
- catch (e) {
918
- if (e && typeof e === 'object' && 'id' in e && 'message' in e) {
919
- mathErrors.push({
920
- range: createNodeRange(lastChild),
921
- severity: 2,
922
- source: 'MathJax',
923
- code: e.id,
924
- message: e.message,
925
- });
926
- }
927
- }
928
- }
929
- return mathErrors;
930
- });
931
893
  /* NOT FOR BROWSER ONLY END */
932
894
  return [
933
895
  diagnostics,
@@ -935,7 +897,6 @@ class LanguageService {
935
897
  jsonDiagnostics,
936
898
  /* NOT FOR BROWSER ONLY */
937
899
  lilypondDiagnostics,
938
- mathDiagnostics,
939
900
  ].flat(2);
940
901
  }
941
902
  /**
@@ -1013,7 +974,7 @@ class LanguageService {
1013
974
  if (!selfClosing) {
1014
975
  const foldingRanges = document_1.jsonLSP.getFoldingRanges(new document_1.EmbeddedJSONDocument(root, lastChild));
1015
976
  if (foldingRanges.length > 0) {
1016
- ranges.push(...foldingRanges);
977
+ Array.prototype.push.apply(ranges, foldingRanges);
1017
978
  }
1018
979
  }
1019
980
  }
@@ -1031,7 +992,7 @@ class LanguageService {
1031
992
  this.config ??= index_1.default.getConfig();
1032
993
  const { articlePath, protocol } = this.config, absolute = articlePath?.includes('//'), protocolRegex = getLinkRegex(protocol);
1033
994
  return (await this.#queue(text))
1034
- .querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module,magic-word#filepath,magic-word#widget' : ''}`)
995
+ .querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value${absolute ? ',link-target,template-name,invoke-module,magic-word#filepath,magic-word#widget' : ''},image-parameter#link,image-parameter#manualthumb`)
1035
996
  .reverse()
1036
997
  .map((token) => {
1037
998
  let name;
@@ -1039,11 +1000,16 @@ class LanguageService {
1039
1000
  ({ name } = token);
1040
1001
  token = token.childNodes[1].lastChild; // eslint-disable-line no-param-reassign
1041
1002
  }
1003
+ else if (token.is('image-parameter')) {
1004
+ ({ name } = token);
1005
+ }
1042
1006
  const { type, parentNode, firstChild, lastChild, childNodes, length } = token, { tag } = parentNode;
1043
1007
  name ??= parentNode.name;
1044
1008
  if (!(type !== 'attr-value'
1009
+ || name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag)
1045
1010
  || name === 'src' && ['templatestyles', 'img'].includes(tag)
1046
- || name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag))
1011
+ || name === 'templatename' && tag === 'rss'
1012
+ || name === 'file' && tag === 'phonos')
1047
1013
  || !isPlain(token)) {
1048
1014
  return false;
1049
1015
  }
@@ -1068,29 +1034,29 @@ class LanguageService {
1068
1034
  }
1069
1035
  target = parentNode.link.getUrl(articlePath);
1070
1036
  }
1071
- else if (type === 'template-name') {
1037
+ else if (type === 'link-target' || type === 'template-name') {
1072
1038
  target = parentNode.getAttribute('title').getUrl(articlePath);
1073
1039
  }
1074
- else if (['link-target', 'invoke-module', 'parameter-value'].includes(type)
1075
- || type === 'attr-value' && name === 'src' && tag === 'templatestyles'
1076
- || type === 'image-parameter' && !protocolRegex.test(target)) {
1040
+ else if (['invoke-module', 'parameter-value'].includes(type)
1041
+ || type === 'attr-value' && (name === 'src' && tag === 'templatestyles'
1042
+ || name === 'templatename' && tag === 'rss'
1043
+ || name === 'file' && tag === 'phonos')
1044
+ || type === 'image-parameter' && (name === 'manualthumb' || !protocolRegex.test(target))) {
1077
1045
  if (!absolute || target.startsWith('/')) {
1078
1046
  return false;
1079
1047
  }
1080
- let ns = 0;
1081
- switch (type) {
1082
- case 'attr-value':
1083
- ns = 10;
1084
- break;
1085
- case 'invoke-module':
1086
- ns = 828;
1087
- break;
1088
- case 'parameter-value':
1089
- ns = name === 'filepath' ? 6 : 274;
1090
- // no default
1048
+ else if (type === 'image-parameter' && name === 'manualthumb'
1049
+ || type === 'parameter-value' && name === 'filepath'
1050
+ || type === 'attr-value' && tag === 'phonos') {
1051
+ target = `File:${target}`;
1052
+ }
1053
+ else if (type === 'parameter-value') {
1054
+ target = `Widget:${target}`;
1055
+ }
1056
+ else if (type === 'invoke-module') {
1057
+ target = `Module:${target}`;
1091
1058
  }
1092
- const title = index_1.default
1093
- .normalizeTitle(target, ns, false, this.config, { temporary: true });
1059
+ const title = index_1.default.normalizeTitle(target, type === 'attr-value' ? 10 : 0, false, this.config, { temporary: true });
1094
1060
  if (!title.valid) {
1095
1061
  return false;
1096
1062
  }
@@ -1269,7 +1235,7 @@ class LanguageService {
1269
1235
  }
1270
1236
  /* NOT FOR BROWSER ONLY */
1271
1237
  }
1272
- else if ((0, exports.isAttr)(offsetNode, true)) {
1238
+ else if (isAttr(offsetNode, true)) {
1273
1239
  const textDoc = new document_1.EmbeddedCSSDocument(root, offsetNode);
1274
1240
  return document_1.cssLSP.doHover(textDoc, position, textDoc.styleSheet) ?? undefined;
1275
1241
  }
@@ -1392,7 +1358,7 @@ class LanguageService {
1392
1358
  }
1393
1359
  /** @private */
1394
1360
  findStyleTokens() {
1395
- return this.#done.querySelectorAll(cssSelector).filter(({ lastChild }) => (0, exports.isAttr)(lastChild));
1361
+ return this.#done.querySelectorAll(cssSelector).filter(({ lastChild }) => isAttr(lastChild));
1396
1362
  }
1397
1363
  /**
1398
1364
  * Provide refactoring actions
package/dist/lib/node.js CHANGED
@@ -33,9 +33,13 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
33
33
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
34
  done = true;
35
35
  };
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
36
39
  Object.defineProperty(exports, "__esModule", { value: true });
37
40
  exports.AstNode = void 0;
38
41
  /* eslint-disable @typescript-eslint/no-base-to-string */
42
+ const search_1 = __importDefault(require("../util/search"));
39
43
  const lint_1 = require("../util/lint");
40
44
  const debug_1 = require("../util/debug");
41
45
  const cached_1 = require("../mixin/cached");
@@ -160,7 +164,7 @@ let AstNode = (() => {
160
164
  const { length } = String(this);
161
165
  index += index < 0 ? length : 0;
162
166
  if (index >= 0 && index <= length) {
163
- const lines = this.getLines(), top = lines.findIndex(([, , end]) => index <= end);
167
+ const lines = this.getLines(), top = (0, search_1.default)(lines, index, ([, , end], needle) => end - needle);
164
168
  return { top, left: index - lines[top][1] };
165
169
  }
166
170
  return undefined;
@@ -184,25 +188,23 @@ let AstNode = (() => {
184
188
  * @param j rank of the child node / 子节点序号
185
189
  */
186
190
  getRelativeIndex(j) {
187
- LINT: { // eslint-disable-line no-unused-labels
188
- if (j === undefined) {
189
- const { parentNode } = this;
190
- return parentNode
191
- ? parentNode.getRelativeIndex(parentNode.childNodes.indexOf(this))
192
- : 0;
193
- }
194
- return (0, lint_1.cache)(this.#rIndex[j], () => {
195
- const { childNodes } = this, n = j + (j < 0 ? childNodes.length : 0);
196
- let acc = this.getAttribute('padding');
197
- for (let i = 0; i < n; i++) {
198
- this.#rIndex[i] = [debug_1.Shadow.rev, acc];
199
- acc += childNodes[i].toString().length + this.getGaps(i);
200
- }
201
- return acc;
202
- }, value => {
203
- this.#rIndex[j] = value;
204
- });
191
+ if (j === undefined) {
192
+ const { parentNode } = this;
193
+ return parentNode
194
+ ? parentNode.getRelativeIndex(parentNode.childNodes.indexOf(this))
195
+ : 0;
205
196
  }
197
+ return (0, lint_1.cache)(this.#rIndex[j], () => {
198
+ const { childNodes } = this, n = j + (j < 0 ? childNodes.length : 0);
199
+ let acc = this.getAttribute('padding');
200
+ for (let i = 0; i < n; i++) {
201
+ this.#rIndex[i] = [debug_1.Shadow.rev, acc];
202
+ acc += childNodes[i].toString().length + this.getGaps(i);
203
+ }
204
+ return acc;
205
+ }, value => {
206
+ this.#rIndex[j] = value;
207
+ });
206
208
  }
207
209
  /**
208
210
  * Get the absolute character index of the current node
@@ -210,7 +212,8 @@ let AstNode = (() => {
210
212
  * 获取当前节点的绝对位置
211
213
  */
212
214
  getAbsoluteIndex() {
213
- LINT: return (0, lint_1.cache)(// eslint-disable-line no-unused-labels
215
+ // 也用于Prism-Wiki
216
+ return (0, lint_1.cache)(// eslint-disable-line no-unused-labels
214
217
  this.#aIndex, () => {
215
218
  const { parentNode } = this;
216
219
  return parentNode ? parentNode.getAbsoluteIndex() + this.getRelativeIndex() : 0;
@@ -4,6 +4,7 @@ export interface TitleOptions {
4
4
  decode?: boolean | undefined;
5
5
  selfLink?: boolean | undefined;
6
6
  halfParsed?: boolean | undefined;
7
+ page?: string | undefined;
7
8
  }
8
9
  /**
9
10
  * title object of a MediaWiki page
@@ -41,8 +42,9 @@ export declare class Title {
41
42
  * @param opt.temporary 是否是临时标题
42
43
  * @param opt.decode 是否需要解码
43
44
  * @param opt.selfLink 是否允许selfLink
45
+ * @param opt.page 当前页面标题
44
46
  */
45
- constructor(title: string, defaultNs: number, config: Config, { temporary, decode, selfLink }?: TitleOptions);
47
+ constructor(title: string, defaultNs: number, config: Config, { temporary, decode, selfLink, page }?: TitleOptions);
46
48
  /**
47
49
  * Check if the title is a redirect
48
50
  *
package/dist/lib/title.js CHANGED
@@ -3,6 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Title = void 0;
4
4
  const common_1 = require("@bhsd/common");
5
5
  const string_1 = require("../util/string");
6
+ /**
7
+ * 解析标题的路径
8
+ * @param title 标题
9
+ */
10
+ const resolve = (title) => {
11
+ const [, { length }, sub] = /^((?:\.\.\/)*)([\s\S]*)/u.exec(title);
12
+ return [length / 3, sub];
13
+ };
6
14
  /**
7
15
  * title object of a MediaWiki page
8
16
  *
@@ -14,6 +22,8 @@ class Title {
14
22
  #path;
15
23
  #ns;
16
24
  #fragment;
25
+ /** @private */
26
+ page;
17
27
  valid;
18
28
  /** @private */
19
29
  encoded = false;
@@ -62,9 +72,11 @@ class Title {
62
72
  * @param opt.temporary 是否是临时标题
63
73
  * @param opt.decode 是否需要解码
64
74
  * @param opt.selfLink 是否允许selfLink
75
+ * @param opt.page 当前页面标题
65
76
  */
66
- constructor(title, defaultNs, config, { temporary, decode, selfLink } = {}) {
67
- const subpage = title.trim().startsWith('../');
77
+ constructor(title, defaultNs, config, { temporary, decode, selfLink, page } = {}) {
78
+ this.page = page;
79
+ const trimmed = title.trim(), subpage = trimmed.startsWith('../');
68
80
  if (decode && title.includes('%')) {
69
81
  try {
70
82
  const encoded = /%(?!21|3[ce]|5[bd]|7[b-d])[\da-f]{2}/iu.test(title);
@@ -74,7 +86,7 @@ class Title {
74
86
  catch { }
75
87
  }
76
88
  title = (0, string_1.decodeHtml)(title).replace(/[_ ]+/gu, ' ').trim();
77
- if (subpage) {
89
+ if (subpage || page && trimmed.startsWith('/')) {
78
90
  this.#ns = 0;
79
91
  }
80
92
  else {
@@ -105,10 +117,12 @@ class Title {
105
117
  this.#fragment = fragment.replace(/ /gu, '_');
106
118
  title = title.slice(0, i).trim();
107
119
  }
120
+ const [level, sub] = subpage ? resolve(title) : [0, title];
108
121
  this.valid = Boolean(title
109
122
  || selfLink && this.ns === 0 && this.#fragment !== undefined)
110
123
  && (0, string_1.decodeHtml)(title) === title
111
- && !/^:|\0\d+[eh!+-]\x7F|[<>[\]{}|\n]|%[\da-f]{2}|(?:^|\/)\.{1,2}(?:$|\/)/iu.test(subpage ? /^(?:\.\.\/)+(.*)/u.exec(title)[1] : title);
124
+ && (level === 0 || page === undefined || page.split('/', level + 1).length > level)
125
+ && !/^:|\0\d+[eh!+-]\x7F|[<>[\]{}|\n]|%[\da-f]{2}|(?:^|\/)\.{1,2}(?:$|\/)/iu.test(sub);
112
126
  this.main = title;
113
127
  this.#namespaces = config.namespaces;
114
128
  this.#path = config.articlePath || '/wiki/$1';
@@ -116,6 +130,23 @@ class Title {
116
130
  this.#path += `${this.#path.endsWith('/') ? '' : '/'}$1`;
117
131
  }
118
132
  }
133
+ /**
134
+ * 生成标题
135
+ * @param prefix 前缀
136
+ */
137
+ #getTitle(prefix) {
138
+ let title = (prefix + this.main).replace(/ /gu, '_');
139
+ if (title.startsWith('/')) {
140
+ title = (this.page ?? '') + title.replace(/(.)\/$/u, '$1');
141
+ }
142
+ else if (title.startsWith('../') && this.page?.includes('/')) {
143
+ const [level, sub] = resolve(title), dirs = this.page.split('/');
144
+ if (dirs.length > level) {
145
+ title = dirs.slice(0, -level).join('/') + (sub && '/') + sub;
146
+ }
147
+ }
148
+ return [false, title];
149
+ }
119
150
  /**
120
151
  * Check if the title is a redirect
121
152
  *
@@ -123,11 +154,8 @@ class Title {
123
154
  * @since v1.12.2
124
155
  */
125
156
  getRedirection() {
126
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
127
- const prefix = this.prefix;
128
- // eslint-disable-next-line prefer-const
129
- let title = (prefix + this.main).replace(/ /gu, '_');
130
- return [false, title];
157
+ const { prefix, } = this, pre = prefix, result = this.#getTitle(pre);
158
+ return result;
131
159
  }
132
160
  /** @private */
133
161
  setFragment(fragment) {