@diplodoc/transform 4.17.2 → 4.19.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 (42) hide show
  1. package/dist/css/print.css +12 -0
  2. package/dist/css/print.css.map +3 -3
  3. package/dist/css/yfm.css +83 -2
  4. package/dist/css/yfm.css.map +3 -3
  5. package/dist/css/yfm.min.css +1 -1
  6. package/dist/css/yfm.min.css.map +3 -3
  7. package/dist/js/yfm.js +99 -73
  8. package/dist/js/yfm.js.map +3 -3
  9. package/dist/js/yfm.min.js +1 -1
  10. package/dist/js/yfm.min.js.map +3 -3
  11. package/lib/liquid/conditions.js +1 -1
  12. package/lib/liquid/conditions.js.map +1 -1
  13. package/lib/liquid/index.js +3 -1
  14. package/lib/liquid/index.js.map +1 -1
  15. package/lib/liquid/lexical.d.ts +2 -0
  16. package/lib/liquid/lexical.js +4 -1
  17. package/lib/liquid/lexical.js.map +1 -1
  18. package/lib/liquid/substitutions.d.ts +1 -1
  19. package/lib/liquid/substitutions.js +29 -9
  20. package/lib/liquid/substitutions.js.map +1 -1
  21. package/lib/plugins/checkbox/checkbox.d.ts +14 -2
  22. package/lib/plugins/checkbox/checkbox.js +22 -10
  23. package/lib/plugins/checkbox/checkbox.js.map +1 -1
  24. package/lib/plugins/checkbox/index.d.ts +2 -2
  25. package/lib/plugins/checkbox/index.js.map +1 -1
  26. package/lib/plugins/table/index.js +160 -4
  27. package/lib/plugins/table/index.js.map +1 -1
  28. package/package.json +2 -2
  29. package/src/scss/_anchor.scss +2 -2
  30. package/src/scss/_table.scss +32 -0
  31. package/src/scss/print/cut.scss +1 -0
  32. package/src/scss/print/tabs.scss +6 -0
  33. package/src/scss/print/term.scss +10 -0
  34. package/src/scss/print.scss +2 -1
  35. package/src/scss/yfm.scss +1 -0
  36. package/src/transform/liquid/conditions.ts +1 -1
  37. package/src/transform/liquid/index.ts +4 -1
  38. package/src/transform/liquid/lexical.ts +2 -0
  39. package/src/transform/liquid/substitutions.ts +43 -9
  40. package/src/transform/plugins/checkbox/checkbox.ts +26 -11
  41. package/src/transform/plugins/checkbox/index.ts +2 -2
  42. package/src/transform/plugins/table/index.ts +178 -6
@@ -1,5 +1,6 @@
1
1
  import StateBlock from 'markdown-it/lib/rules_block/state_block';
2
2
  import {MarkdownItPluginCb} from '../typings';
3
+ import Token from 'markdown-it/lib/token';
3
4
 
4
5
  const pluginName = 'yfm_table';
5
6
  const pipeChar = 0x7c; // |
@@ -91,12 +92,17 @@ class StateIterator {
91
92
  }
92
93
  }
93
94
 
94
- function getTableRows(
95
+ interface RowPositions {
96
+ rows: [number, number, [Stats, Stats][]][];
97
+ endOfTable: number | null;
98
+ }
99
+
100
+ function getTableRowPositions(
95
101
  state: StateBlock,
96
102
  startPosition: number,
97
103
  endPosition: number,
98
104
  startLine: number,
99
- ) {
105
+ ): RowPositions {
100
106
  let endOfTable = null;
101
107
  let tableLevel = 0;
102
108
  let currentRow: [Stats, Stats][] = [];
@@ -210,6 +216,144 @@ function getTableRows(
210
216
  return {rows, endOfTable};
211
217
  }
212
218
 
219
+ /**
220
+ * Removes the specified attribute from attributes in the content of a token.
221
+ *
222
+ * @param {Token} contentToken - The target token.
223
+ * @param {string} attr - The attribute to be removed from the token content.
224
+ *
225
+ * @return {void}
226
+ */
227
+ function removeAttrFromTokenContent(contentToken: Token, attr: string): void {
228
+ // Replace the attribute in the token content with an empty string.
229
+ const blockRegex = /\s*\{[^}]*}/;
230
+ const allAttrs = contentToken.content.match(blockRegex);
231
+ if (!allAttrs) {
232
+ return;
233
+ }
234
+ let replacedContent = allAttrs[0].replace(`.${attr}`, '');
235
+ if (replacedContent.trim() === '{}') {
236
+ replacedContent = '';
237
+ }
238
+ contentToken.content = contentToken.content.replace(allAttrs[0], replacedContent);
239
+ }
240
+
241
+ /**
242
+ * Extracts the class attribute from the given content token and applies it to the tdOpenToken.
243
+ * Preserves other attributes.
244
+ *
245
+ * @param {Token} contentToken - Search the content of this token for the class.
246
+ * @param {Token} tdOpenToken - Parent td_open token. Extracted class is applied to this token.
247
+ * @returns {void}
248
+ */
249
+ function extractAndApplyClassFromToken(contentToken: Token, tdOpenToken: Token): void {
250
+ // Regex to find class attribute in any position within brackets
251
+ const classAttrRegex = /(?<=\{[^}]*)\.([-_a-zA-Z0-9]+)/g;
252
+ const classAttrMatch = classAttrRegex.exec(contentToken.content);
253
+ if (classAttrMatch) {
254
+ const classAttr = classAttrMatch[1];
255
+ tdOpenToken.attrSet('class', classAttr);
256
+ removeAttrFromTokenContent(contentToken, classAttr);
257
+ }
258
+ }
259
+
260
+ const COLSPAN_SYMBOL = '>';
261
+ const ROWSPAN_SYMBOL = '^';
262
+
263
+ /**
264
+ * Traverses through the content map, applying row/colspan attributes and marking the special cells for deletion.
265
+ * Upon encountering a symbol denoting a row span or a column span, proceed backwards in row or column
266
+ * until text cell is found. Upon finding the text cell, store the colspan or rowspan value.
267
+ * During the backward traversal, if the same symbol is encountered, increment the value of rowspan/colspan.
268
+ * Colspan symbol is ignored for the first column. Rowspan symbol is ignored for the first row
269
+ *
270
+ * @param contentMap string[][]
271
+ * @param tokenMap Token[][]
272
+ * @return {void}
273
+ */
274
+ const applySpans = (contentMap: string[][], tokenMap: Token[][]): void => {
275
+ for (let i = 0; i < contentMap.length; i++) {
276
+ for (let j = 0; j < contentMap[0].length; j++) {
277
+ if (contentMap[i][j] === COLSPAN_SYMBOL) {
278
+ // skip the first column
279
+ if (j === 0) {
280
+ continue;
281
+ }
282
+ tokenMap[i][j].meta = {markForDeletion: true};
283
+ let colspanFactor = 2;
284
+ // traverse columns backwards
285
+ for (let col = j - 1; col >= 0; col--) {
286
+ if (contentMap[i][col] === COLSPAN_SYMBOL) {
287
+ colspanFactor++;
288
+ tokenMap[i][col].meta = {markForDeletion: true};
289
+ } else if (contentMap[i][col] === ROWSPAN_SYMBOL) {
290
+ // Do nothing, this should be applied on the row that's being extended
291
+ break;
292
+ } else {
293
+ tokenMap[i][col].attrSet('colspan', colspanFactor.toString());
294
+ break;
295
+ }
296
+ }
297
+ }
298
+
299
+ if (contentMap[i][j] === ROWSPAN_SYMBOL) {
300
+ // skip the first row
301
+ if (i === 0) {
302
+ continue;
303
+ }
304
+ tokenMap[i][j].meta = {markForDeletion: true};
305
+ let rowSpanFactor = 2;
306
+ // traverse rows upward
307
+ for (let row = i - 1; row >= 0; row--) {
308
+ if (contentMap[row][j] === ROWSPAN_SYMBOL) {
309
+ rowSpanFactor++;
310
+ tokenMap[row][j].meta = {markForDeletion: true};
311
+ } else if (contentMap[row][j] === COLSPAN_SYMBOL) {
312
+ break;
313
+ } else {
314
+ tokenMap[row][j].attrSet('rowspan', rowSpanFactor.toString());
315
+ break;
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+ };
322
+
323
+ /**
324
+ * Removes td_open and matching td_close tokens and the content within them
325
+ *
326
+ * @param {number} tableStart - The index of the start of the table in the state tokens array.
327
+ * @param {Token[]} tokens - The array of tokens from state.
328
+ * @returns {void}
329
+ */
330
+ const clearTokens = (tableStart: number, tokens: Token[]): void => {
331
+ // use splices array to avoid modifying the tokens array during iteration
332
+ const splices: number[][] = [];
333
+ for (let i = tableStart; i < tokens.length; i++) {
334
+ if (tokens[i].meta?.markForDeletion) {
335
+ // Use unshift instead of push so that the splices indexes are in reverse order.
336
+ // Reverse order guarantees that we don't mess up the indexes while removing the items.
337
+ splices.unshift([i]);
338
+ const level = tokens[i].level;
339
+ // find matching td_close with the same level
340
+ for (let j = i + 1; j < tokens.length; j++) {
341
+ if (tokens[j].type === 'yfm_td_close' && tokens[j].level === level) {
342
+ splices[0].push(j);
343
+ break;
344
+ }
345
+ }
346
+ }
347
+ }
348
+ splices.forEach(([start, end]) => {
349
+ // check that we have both start and end defined
350
+ // it's possible we didn't find td_close index
351
+ if (start && end) {
352
+ tokens.splice(start, end - start + 1);
353
+ }
354
+ });
355
+ };
356
+
213
357
  const yfmTable: MarkdownItPluginCb = (md) => {
214
358
  md.block.ruler.before(
215
359
  'code',
@@ -232,7 +376,12 @@ const yfmTable: MarkdownItPluginCb = (md) => {
232
376
  return true;
233
377
  }
234
378
 
235
- const {rows, endOfTable} = getTableRows(state, startPosition, endPosition, startLine);
379
+ const {rows, endOfTable} = getTableRowPositions(
380
+ state,
381
+ startPosition,
382
+ endPosition,
383
+ startLine,
384
+ );
236
385
 
237
386
  if (!endOfTable) {
238
387
  token = state.push('__yfm_lint', '', 0);
@@ -247,6 +396,7 @@ const yfmTable: MarkdownItPluginCb = (md) => {
247
396
  state.lineMax = endOfTable;
248
397
  state.line = startLine;
249
398
 
399
+ const tableStart = state.tokens.length;
250
400
  token = state.push('yfm_table_open', 'table', 1);
251
401
  token.map = [startLine, endOfTable];
252
402
 
@@ -255,9 +405,18 @@ const yfmTable: MarkdownItPluginCb = (md) => {
255
405
 
256
406
  const maxRowLength = Math.max(...rows.map(([, , cols]) => cols.length));
257
407
 
408
+ // cellsMaps is a 2-D map of all td_open tokens in the table.
409
+ // cellsMap is used to access the table cells by [row][column] coordinates
410
+ const cellsMap: Token[][] = [];
411
+
412
+ // contentMap is a 2-D map of the text content within cells in the table.
413
+ // To apply spans, traverse the contentMap and modify the cells from cellsMap
414
+ const contentMap: string[][] = [];
415
+
258
416
  for (let i = 0; i < rows.length; i++) {
259
417
  const [rowLineStarts, rowLineEnds, cols] = rows[i];
260
-
418
+ cellsMap.push([]);
419
+ contentMap.push([]);
261
420
  const rowLength = cols.length;
262
421
 
263
422
  token = state.push('yfm_tr_open', 'tr', 1);
@@ -266,6 +425,7 @@ const yfmTable: MarkdownItPluginCb = (md) => {
266
425
  for (let j = 0; j < cols.length; j++) {
267
426
  const [begin, end] = cols[j];
268
427
  token = state.push('yfm_td_open', 'td', 1);
428
+ cellsMap[i].push(token);
269
429
  token.map = [begin.line, end.line];
270
430
 
271
431
  const oldTshift = state.tShift[begin.line];
@@ -279,14 +439,23 @@ const yfmTable: MarkdownItPluginCb = (md) => {
279
439
  state.lineMax = end.line + 1;
280
440
 
281
441
  state.md.block.tokenize(state, begin.line, end.line + 1);
442
+ const contentToken = state.tokens[state.tokens.length - 2];
443
+
444
+ // In case of ">" within a cell without whitespace it gets consumed as a blockquote.
445
+ // To handle that, check markup as well
446
+ const content = contentToken.content.trim() || contentToken.markup.trim();
447
+ contentMap[i].push(content);
448
+
449
+ token = state.push('yfm_td_close', 'td', -1);
450
+ state.tokens[state.tokens.length - 1].map = [end.line, end.line + 1];
282
451
 
283
452
  state.lineMax = oldLineMax;
284
453
  state.tShift[begin.line] = oldTshift;
285
454
  state.bMarks[begin.line] = oldBMark;
286
455
  state.eMarks[end.line] = oldEMark;
287
456
 
288
- token = state.push('yfm_td_close', 'td', -1);
289
- state.tokens[state.tokens.length - 1].map = [end.line, end.line + 1];
457
+ const rowTokens = cellsMap[cellsMap.length - 1];
458
+ extractAndApplyClassFromToken(contentToken, rowTokens[rowTokens.length - 1]);
290
459
  }
291
460
 
292
461
  if (rowLength < maxRowLength) {
@@ -300,6 +469,9 @@ const yfmTable: MarkdownItPluginCb = (md) => {
300
469
  token = state.push('yfm_tr_close', 'tr', -1);
301
470
  }
302
471
 
472
+ applySpans(contentMap, cellsMap);
473
+ clearTokens(tableStart, state.tokens);
474
+
303
475
  token = state.push('yfm_tbody_close', 'tbody', -1);
304
476
 
305
477
  token = state.push('yfm_table_close', 'table', -1);