@diplodoc/transform 4.69.1 → 4.69.3

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,127 @@ 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
+ const labelContent = state.src.slice(state.pos + 1, match.labelEnd).replace(/\\(.)/g, '$1');
111
+
112
+ const termOpen = state.push('term_open', 'i', 1);
113
+ setTermAttrs(termOpen, match.termId);
114
+
115
+ const textToken = state.push('text', '', 0);
116
+ textToken.content = labelContent;
117
+
118
+ state.push('term_close', 'i', -1);
119
+ }
120
+
121
+ state.pos = match.endPos;
122
+ return true;
123
+ }
124
+
10
125
  const term: MarkdownItPluginCb = (md, options) => {
11
126
  const escapeRE = md.utils.escapeRE;
12
127
  const arrayReplaceAt = md.utils.arrayReplaceAt;
13
128
 
14
129
  const {isLintRun} = options;
15
- // Don't parse urls that starts with *
130
+
131
+ // Prevent * URLs from being parsed as regular links (backward compatibility)
16
132
  const defaultLinkValidation = md.validateLink;
17
133
  md.validateLink = function (url) {
18
134
  if (url.startsWith('*')) {
@@ -22,8 +138,11 @@ const term: MarkdownItPluginCb = (md, options) => {
22
138
  return defaultLinkValidation(url);
23
139
  };
24
140
 
141
+ // Inline rule: handles [text](*termId) before emphasis can interfere with *
142
+ md.inline.ruler.before('link', 'term_inline', termInlineRule);
143
+
25
144
  function termReplace(state: StateCore) {
26
- let i, j, l, tokens, token, text, nodes, pos, term, currentToken;
145
+ let i, j, l, tokens, token, text, nodes, pos, termMatch, currentToken;
27
146
 
28
147
  const blockTokens = state.tokens;
29
148
 
@@ -61,7 +180,7 @@ const term: MarkdownItPluginCb = (md, options) => {
61
180
  continue;
62
181
  }
63
182
 
64
- if (!(currentToken.type === 'text')) {
183
+ if (currentToken.type !== 'text') {
65
184
  continue;
66
185
  }
67
186
 
@@ -87,23 +206,18 @@ const term: MarkdownItPluginCb = (md, options) => {
87
206
  nodes.push(token);
88
207
  }
89
208
 
90
- while ((term = reg.exec(text))) {
91
- const termTitle = term[1];
92
- const termKey = term[3];
209
+ while ((termMatch = reg.exec(text))) {
210
+ const termTitle = termMatch[1];
211
+ const termKey = termMatch[3];
93
212
 
94
- if (term.index > 0 || term[1].length > 0) {
213
+ if (termMatch.index > 0 || termMatch[1].length > 0) {
95
214
  token = new state.Token('text', '', 0);
96
- token.content = text.slice(pos, term.index);
215
+ token.content = text.slice(pos, termMatch.index);
97
216
  nodes.push(token);
98
217
  }
99
218
 
100
219
  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());
220
+ setTermAttrs(token, termKey);
107
221
  nodes.push(token);
108
222
 
109
223
  token = new state.Token('text', '', 0);