@diplodoc/transform 4.65.3 → 4.66.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.
@@ -15,12 +15,20 @@ export const CheckboxTokenType = {
15
15
  CheckboxLabelClose: 'checkbox_label_close',
16
16
  } as const;
17
17
 
18
+ type ParsedCheckbox = {
19
+ tokens: Token[];
20
+ startLine: number;
21
+ endLine: number;
22
+ checked: boolean;
23
+ };
24
+
18
25
  function matchOpenToken(tokens: Token[], i: number) {
19
- return (
20
- tokens[i].type === 'paragraph_open' &&
21
- tokens[i + 1].type === 'inline' &&
22
- tokens[i + 1].content.match(pattern)
23
- );
26
+ if (tokens[i].type !== 'paragraph_open' || tokens[i + 1].type !== 'inline') {
27
+ return false;
28
+ }
29
+
30
+ const firstInline = tokens[i + 1].children?.[0];
31
+ return firstInline?.type === 'text' && pattern.test(firstInline.content);
24
32
  }
25
33
 
26
34
  export type CheckboxOptions = {
@@ -39,16 +47,16 @@ export const checkboxReplace = function (_md: MarkdownIt, opts?: CheckboxOptions
39
47
  };
40
48
  const options = Object.assign(defaults, opts);
41
49
 
42
- const createTokens = function (state: StateCore, checked: boolean, label: string, i: number) {
50
+ const createTokens = function (state: StateCore, checkbox: ParsedCheckbox): Token[] {
43
51
  let token: Token;
44
- const nodes = [];
52
+ const nodes: Token[] = [];
45
53
 
46
54
  /**
47
55
  * <div class="checkbox">
48
56
  */
49
57
  token = new state.Token(CheckboxTokenType.CheckboxOpen, 'div', 1);
50
58
  token.block = true;
51
- token.map = state.tokens[i].map;
59
+ token.map = [checkbox.startLine, checkbox.endLine];
52
60
  token.attrs = [['class', options.divClass]];
53
61
  nodes.push(token);
54
62
 
@@ -59,7 +67,7 @@ export const checkboxReplace = function (_md: MarkdownIt, opts?: CheckboxOptions
59
67
  lastId += 1;
60
68
  token = new state.Token(CheckboxTokenType.CheckboxInput, 'input', 0);
61
69
  token.block = true;
62
- token.map = state.tokens[i].map;
70
+ token.map = [checkbox.startLine, checkbox.endLine];
63
71
  token.attrs = [
64
72
  ['type', 'checkbox'],
65
73
  ['id', id],
@@ -67,7 +75,7 @@ export const checkboxReplace = function (_md: MarkdownIt, opts?: CheckboxOptions
67
75
  if (options.disabled) {
68
76
  token.attrSet('disabled', '');
69
77
  }
70
- if (checked === true) {
78
+ if (checkbox.checked === true) {
71
79
  token.attrSet('checked', 'true');
72
80
  }
73
81
  nodes.push(token);
@@ -82,41 +90,111 @@ export const checkboxReplace = function (_md: MarkdownIt, opts?: CheckboxOptions
82
90
  /**
83
91
  * content of label tag
84
92
  */
85
- token = state.md.parseInline(label, state.env)[0];
86
- nodes.push(token);
93
+ nodes.push(...checkbox.tokens);
87
94
 
88
95
  /**
89
96
  * closing tags
90
97
  */
91
98
  token = new state.Token(CheckboxTokenType.CheckboxLabelClose, 'label', -1);
92
99
  token.block = true;
93
- token.map = state.tokens[i].map;
100
+ token.map = [checkbox.startLine, checkbox.endLine];
94
101
  nodes.push(token);
95
102
  token = new state.Token(CheckboxTokenType.CheckboxClose, 'div', -1);
96
103
  token.block = true;
97
- token.map = state.tokens[i].map;
104
+ token.map = [checkbox.startLine, checkbox.endLine];
98
105
  nodes.push(token);
99
106
 
100
107
  return nodes;
101
108
  };
102
- const splitTextToken = function (state: StateCore, matches: RegExpMatchArray, i: number) {
103
- let checked = false;
104
- const value = matches[1];
105
- const label = matches[2];
106
- if (value === 'X' || value === 'x') {
107
- checked = true;
108
- }
109
- return createTokens(state, checked, label, i);
110
- };
111
109
  return function (state) {
112
110
  const blockTokens = state.tokens;
113
111
  for (let i = 0; i < blockTokens.length; i++) {
114
- const match = matchOpenToken(blockTokens, i);
115
- if (!match) {
112
+ if (!matchOpenToken(blockTokens, i)) {
116
113
  continue;
117
114
  }
118
115
 
119
- blockTokens.splice(i, 3, ...splitTextToken(state, match, i));
116
+ const pToken = blockTokens[i];
117
+ const startLine = pToken.map?.[0] ?? NaN;
118
+
119
+ const checkboxes = parseInlineContent(blockTokens[i + 1], startLine);
120
+
121
+ const checkboxTokens: Token[] = [];
122
+ for (const checkbox of checkboxes) {
123
+ const first = checkbox.tokens[0];
124
+ // remove checkbox markup [X]␣ at start of text content
125
+ first.content = first.content.slice(4);
126
+ checkboxTokens.push(...createTokens(state, checkbox));
127
+ }
128
+
129
+ // replace paragraph tokens with checkbox tokens
130
+ if (checkboxTokens.length > 0) {
131
+ blockTokens.splice(i, 3, ...checkboxTokens);
132
+ i += checkboxTokens.length - 1;
133
+ }
120
134
  }
121
135
  };
122
136
  };
137
+
138
+ function parseInlineContent(inlineToken: Token, startLine: number): ParsedCheckbox[] {
139
+ const children = inlineToken.children || [];
140
+ const lines = splitInlineTokensByBreaks(children);
141
+ return parseCheckboxesByLines(lines, startLine);
142
+ }
143
+
144
+ function splitInlineTokensByBreaks(tokens: Token[]): Token[][] {
145
+ const lines: Token[][] = [];
146
+ let lineIdx = 0;
147
+ for (const token of tokens) {
148
+ lines[lineIdx] ||= [];
149
+ lines[lineIdx].push(token);
150
+
151
+ if (isBreakToken(token)) {
152
+ lineIdx += 1;
153
+ }
154
+ }
155
+
156
+ return lines;
157
+ }
158
+
159
+ function parseCheckboxesByLines(lines: Token[][], startLine: number): ParsedCheckbox[] {
160
+ const checkboxes: ParsedCheckbox[] = [];
161
+
162
+ let checkboxIdx = -1;
163
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
164
+ const line = lines[lineIdx];
165
+ const first = line?.[0];
166
+ if (!first) {
167
+ continue;
168
+ }
169
+
170
+ let match;
171
+ if (first.type === 'text' && (match = first.content.match(pattern))) {
172
+ const prevLastToken = checkboxes[checkboxIdx]?.tokens.at(-1);
173
+ if (prevLastToken && isBreakToken(prevLastToken)) {
174
+ // remove hanging line breaks
175
+ checkboxes[checkboxIdx].tokens.splice(-1);
176
+ }
177
+
178
+ checkboxIdx += 1;
179
+ checkboxes[checkboxIdx] ||= {
180
+ tokens: [],
181
+ checked: isChecked(match[1]),
182
+ startLine: startLine + lineIdx,
183
+ endLine: startLine + lineIdx + 1,
184
+ };
185
+ }
186
+
187
+ checkboxes[checkboxIdx].tokens.push(...line);
188
+ checkboxes[checkboxIdx].endLine = startLine + lineIdx + 1;
189
+ }
190
+
191
+ return checkboxes;
192
+ }
193
+
194
+ function isChecked(value: string): boolean {
195
+ return value === 'X' || value === 'x';
196
+ }
197
+
198
+ function isBreakToken(token: Token): boolean {
199
+ return token.type === 'softbreak' || token.type === 'hardbreak';
200
+ }