@diplodoc/transform 4.69.1 → 4.69.2

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.
@@ -1,4 +1,5 @@
1
1
  import type StateCore from 'markdown-it/lib/rules_core/state_core';
2
+ import type StateInline from 'markdown-it/lib/rules_inline/state_inline';
2
3
  import type Token from 'markdown-it/lib/token';
3
4
  import type {MarkdownItPluginCb} from '../typings';
4
5
 
@@ -7,12 +8,129 @@ import {generateID} from '../utils';
7
8
  import {termDefinitions} from './termDefinitions';
8
9
  import {BASIC_TERM_REGEXP} from './constants';
9
10
 
11
+ function setTermAttrs(token: Token, termKey: string): void {
12
+ token.attrSet('class', 'yfm yfm-term_title');
13
+ token.attrSet('term-key', ':' + termKey);
14
+ token.attrSet('role', 'button');
15
+ token.attrSet('aria-describedby', ':' + termKey + '_element');
16
+ token.attrSet('tabindex', '0');
17
+ token.attrSet('id', generateID());
18
+ }
19
+
20
+ interface TermMatch {
21
+ labelEnd: number;
22
+ termId: string;
23
+ endPos: number;
24
+ }
25
+
26
+ /**
27
+ * Scans src for [text](*termId) starting at `start`.
28
+ *
29
+ * @param src - source string
30
+ * @param start - position of the opening `[`
31
+ * @param max - maximum position to scan
32
+ * @returns match info or null
33
+ */
34
+ function matchTermPattern(src: string, start: number, max: number): TermMatch | null {
35
+ // Scan for closing ] (no nested [ allowed, matching the original regex)
36
+ let pos = start + 1;
37
+ while (
38
+ pos <= max &&
39
+ src.charCodeAt(pos) !== 0x5d /* ] */ &&
40
+ src.charCodeAt(pos) !== 0x5b /* [ */
41
+ ) {
42
+ if (src.charCodeAt(pos) === 0x5c /* \ */) {
43
+ pos++;
44
+ }
45
+ pos++;
46
+ }
47
+
48
+ if (pos > max || src.charCodeAt(pos) !== 0x5d /* ] */) {
49
+ return null;
50
+ }
51
+
52
+ const labelEnd = pos;
53
+ if (labelEnd === start + 1) {
54
+ return null;
55
+ }
56
+
57
+ // Expect (*
58
+ pos = labelEnd + 1;
59
+ if (
60
+ pos + 1 > max ||
61
+ src.charCodeAt(pos) !== 0x28 /* ( */ ||
62
+ src.charCodeAt(pos + 1) !== 0x2a /* * */
63
+ ) {
64
+ return null;
65
+ }
66
+ pos += 2;
67
+
68
+ // Read term id until )
69
+ const termIdStart = pos;
70
+ while (
71
+ pos <= max &&
72
+ src.charCodeAt(pos) !== 0x29 /* ) */ &&
73
+ src.charCodeAt(pos) !== 0x0a /* \n */
74
+ ) {
75
+ pos++;
76
+ }
77
+
78
+ if (pos > max || src.charCodeAt(pos) !== 0x29 /* ) */) {
79
+ return null;
80
+ }
81
+
82
+ const termId = src.slice(termIdStart, pos);
83
+ if (!termId) {
84
+ return null;
85
+ }
86
+
87
+ return {labelEnd, termId, endPos: pos + 1};
88
+ }
89
+
90
+ /**
91
+ * Inline rule that matches [text](*termId) for defined terms.
92
+ * Runs before the link rule so that * characters inside the
93
+ * pattern are consumed and never trigger emphasis.
94
+ *
95
+ * @param state - inline parser state
96
+ * @param silent - if true, only validate without creating tokens
97
+ * @returns true if the pattern was matched
98
+ */
99
+ function termInlineRule(state: StateInline, silent: boolean): boolean {
100
+ if (state.src.charCodeAt(state.pos) !== 0x5b /* [ */ || !state.env.terms) {
101
+ return false;
102
+ }
103
+
104
+ const match = matchTermPattern(state.src, state.pos, state.posMax);
105
+ if (!match || !state.env.terms[':' + match.termId]) {
106
+ return false;
107
+ }
108
+
109
+ if (silent) {
110
+ return true;
111
+ }
112
+
113
+ const labelContent = state.src.slice(state.pos + 1, match.labelEnd).replace(/\\(.)/g, '$1');
114
+
115
+ const termOpen = state.push('term_open', 'i', 1);
116
+ setTermAttrs(termOpen, match.termId);
117
+
118
+ const textToken = state.push('text', '', 0);
119
+ textToken.content = labelContent;
120
+
121
+ state.push('term_close', 'i', -1);
122
+
123
+ state.pos = match.endPos;
124
+ return true;
125
+ }
126
+
10
127
  const term: MarkdownItPluginCb = (md, options) => {
11
128
  const escapeRE = md.utils.escapeRE;
12
129
  const arrayReplaceAt = md.utils.arrayReplaceAt;
13
130
 
14
131
  const {isLintRun} = options;
15
- // Don't parse urls that starts with *
132
+
133
+ // Prevent * URLs from being parsed as regular links (backward compatibility)
16
134
  const defaultLinkValidation = md.validateLink;
17
135
  md.validateLink = function (url) {
18
136
  if (url.startsWith('*')) {
@@ -22,8 +140,11 @@ const term: MarkdownItPluginCb = (md, options) => {
22
140
  return defaultLinkValidation(url);
23
141
  };
24
142
 
143
+ // Inline rule: handles [text](*termId) before emphasis can interfere with *
144
+ md.inline.ruler.before('link', 'term_inline', termInlineRule);
145
+
25
146
  function termReplace(state: StateCore) {
26
- let i, j, l, tokens, token, text, nodes, pos, term, currentToken;
147
+ let i, j, l, tokens, token, text, nodes, pos, termMatch, currentToken;
27
148
 
28
149
  const blockTokens = state.tokens;
29
150
 
@@ -61,7 +182,7 @@ const term: MarkdownItPluginCb = (md, options) => {
61
182
  continue;
62
183
  }
63
184
 
64
- if (!(currentToken.type === 'text')) {
185
+ if (currentToken.type !== 'text') {
65
186
  continue;
66
187
  }
67
188
 
@@ -87,23 +208,18 @@ const term: MarkdownItPluginCb = (md, options) => {
87
208
  nodes.push(token);
88
209
  }
89
210
 
90
- while ((term = reg.exec(text))) {
91
- const termTitle = term[1];
92
- const termKey = term[3];
211
+ while ((termMatch = reg.exec(text))) {
212
+ const termTitle = termMatch[1];
213
+ const termKey = termMatch[3];
93
214
 
94
- if (term.index > 0 || term[1].length > 0) {
215
+ if (termMatch.index > 0 || termMatch[1].length > 0) {
95
216
  token = new state.Token('text', '', 0);
96
- token.content = text.slice(pos, term.index);
217
+ token.content = text.slice(pos, termMatch.index);
97
218
  nodes.push(token);
98
219
  }
99
220
 
100
221
  token = new state.Token('term_open', 'i', 1);
101
- token.attrSet('class', 'yfm yfm-term_title');
102
- token.attrSet('term-key', ':' + termKey);
103
- token.attrSet('role', 'button');
104
- token.attrSet('aria-describedby', ':' + termKey + '_element');
105
- token.attrSet('tabindex', '0');
106
- token.attrSet('id', generateID());
222
+ setTermAttrs(token, termKey);
107
223
  nodes.push(token);
108
224
 
109
225
  token = new state.Token('text', '', 0);