@adobe/helix-markdown-support 3.1.8 → 4.0.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.
@@ -0,0 +1,91 @@
1
+ /*
2
+ * Copyright 2022 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ /**
14
+ * GridTables remark plugin and micromark extension.
15
+ *
16
+ * GridTables look like this:
17
+ *
18
+ * ```
19
+ * +-------------------+------+
20
+ * | Table Headings | Here |
21
+ * +--------+----------+------+
22
+ * | Sub | Headings | Too |
23
+ * +========+=================+
24
+ * | cell | column spanning |
25
+ * | spans +---------:+------+
26
+ * | rows | normal | cell |
27
+ * +---v----+:---------------:+
28
+ * | | cells can be |
29
+ * | | *formatted* |
30
+ * | | **paragraphs** |
31
+ * | | ``` |
32
+ * | multi | and contain |
33
+ * | line | blocks |
34
+ * | cells | ``` |
35
+ * +========+=========<+======+
36
+ * | footer | cells | |
37
+ * +--------+----------+------+
38
+ * ```
39
+ *
40
+ * - the top of a cell must be indicated by `+-` followed by some `-` or `+` and finished by `-+`.
41
+ * - if the table contains a footer but no header, the top row should use `=` as grid line.
42
+ * - col spans are indicated by missing column (`|`) delimiters
43
+ * - row spans are indicated by missing row (`-`) delimiters
44
+ * - cells can be left, center, right, or justify aligned; indicated by the placement of `:` or `><`
45
+ * - cells can be top, middle, or bottom v-aligned;
46
+ * indicated by the placement of arrows (`v` `^` `x`)
47
+ * - the header and footer sections are delimited by section delimiters (`=`).
48
+ * - if no section delimiters are present, all cells are placed in the table body.
49
+ * - if only 1 section delimiter is present, it delimits header from body.
50
+ * - the content in cells can be a full Markdown document again.
51
+ * note, that the cell boundaries (`|`)
52
+ * need to exactly match with the column markers (`+`) in the row delimiters, if the cell content
53
+ * contains `|`, otherwise the correct layout of the table can't be guaranteed.
54
+ *
55
+ * Layout
56
+ * ======
57
+ *
58
+ * The table layout tries to keep the table within a certain width (default 120). For example,
59
+ * if the table has 3 columns, each column will be max 40 characters wide. If all text in a column
60
+ * is smaller, it will shrink the columns. However, cells have a minimum width (default 10) when
61
+ * text needs to be broken. If the cell contents need more space, e.g. with a nested table or
62
+ * code block, it will grow accordingly.
63
+ *
64
+ * Align
65
+ * =====
66
+ *
67
+ * Horizontal align is indicated by placing markers at the grid line above the cell:
68
+ *
69
+ * ```
70
+ * Justify Center Left Right
71
+ * +>-----<+ +:-----:+ +:------+ +------:+
72
+ * | A b C | | ABC | | ABC | | ABC |
73
+ * +-------+ +-------+ +-------+ +-------+
74
+ * ```
75
+ *
76
+ * Vertical align is indicated by placing markers at the center of the grid line above the cell:
77
+ *
78
+ * ```
79
+ * Top Middle Bottom
80
+ * +---^---+ +---x---+ +---v---+
81
+ * | Larum | | | | |
82
+ * | Ipsum | | Larum | | |
83
+ * | | | Ipsum | | Larum |
84
+ * | | | | | Ipsum |
85
+ * +-------+ +-------+ +-------+
86
+ * ```
87
+ *
88
+ */
89
+ export * from './types.js';
90
+ export { default as remarkGridTable } from './remark-plugin.js';
91
+ export { default as mdast2hastGridTableHandler } from './mdast2hast-handler.js';
@@ -0,0 +1,119 @@
1
+ /*
2
+ * Copyright 2022 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import { all } from 'mdast-util-to-hast';
13
+ import { CONTINUE, SKIP, visit } from 'unist-util-visit';
14
+ import {
15
+ TYPE_BODY, TYPE_CELL, TYPE_FOOTER, TYPE_HEADER, TYPE_ROW,
16
+ } from './types.js';
17
+
18
+ /**
19
+ * @typedef GridTableHandlerOptions
20
+ * @property {boolean} noHeader if true, <thead> and <tbody> elements are suppressed.
21
+ */
22
+
23
+ /**
24
+ * Handles a row (i.e. the `gtRow` node)
25
+ * @return {HastNode} the 'tr' node
26
+ */
27
+ function handleRow(h, node, cellElementName) {
28
+ const cells = [];
29
+ for (const child of node.children) {
30
+ if (child.type === TYPE_CELL) {
31
+ const props = {};
32
+ for (const p of ['colSpan', 'rowSpan', 'align', 'valign']) {
33
+ if (p in child) {
34
+ props[p] = child[p];
35
+ }
36
+ }
37
+ // if cell contains only 1 single paragraph, unwrap it
38
+ if (child.children?.length === 1 && child.children[0].type === 'paragraph') {
39
+ child.children = child.children[0].children;
40
+ }
41
+ const cell = h(child, cellElementName, props, all(h, child));
42
+ cells.push(cell);
43
+
44
+ // clean text elements
45
+ visit(cell, (n) => {
46
+ if (n.tagName === 'code') {
47
+ return SKIP;
48
+ }
49
+ if (n.type === 'text') {
50
+ // eslint-disable-next-line no-param-reassign
51
+ n.value = n.value.replace(/\r?\n/mg, ' ');
52
+ }
53
+ return CONTINUE;
54
+ });
55
+ }
56
+ }
57
+
58
+ return h(node, 'tr', cells);
59
+ }
60
+
61
+ /**
62
+ * Handles a group (array) of rows. eg the children of a `gtBody`.
63
+ * @return {HastNode[]} the array of rows
64
+ */
65
+ function createRows(h, node, cellElementName) {
66
+ const rows = [];
67
+ for (const child of node.children) {
68
+ if (child.type === TYPE_ROW) {
69
+ rows.push(handleRow(h, child, cellElementName));
70
+ }
71
+ }
72
+ return rows;
73
+ }
74
+
75
+ /**
76
+ * Transforms the gridTable to a hast table
77
+ *
78
+ * @param {GridTableHandlerOptions} opts
79
+ * @return {function} A mdast-to-hast handler.
80
+ */
81
+ export default function gridTableHandler(opts = {}) {
82
+ const { noHeader } = opts;
83
+
84
+ return function handleTable(h, node) {
85
+ let headerRows = [];
86
+ let bodyRows = [];
87
+ let footerRows = [];
88
+
89
+ for (const child of node.children) {
90
+ if (child.type === TYPE_HEADER) {
91
+ headerRows = createRows(h, child, 'th');
92
+ } else if (child.type === TYPE_BODY) {
93
+ bodyRows = createRows(h, child, 'td');
94
+ } else if (child.type === TYPE_FOOTER) {
95
+ footerRows = createRows(h, child, 'td');
96
+ } else if (child.type === TYPE_ROW) {
97
+ bodyRows.push(handleRow(h, child, 'td'));
98
+ }
99
+ }
100
+
101
+ let inner;
102
+ if (noHeader && footerRows.length === 0) {
103
+ inner = [...headerRows, ...bodyRows];
104
+ } else {
105
+ inner = [];
106
+ if (headerRows.length) {
107
+ inner.push(h(null, 'thead', headerRows));
108
+ }
109
+ if (bodyRows.length) {
110
+ inner.push(h(null, 'tbody', bodyRows));
111
+ }
112
+ if (footerRows.length) {
113
+ inner.push(h(null, 'tfoot', footerRows));
114
+ }
115
+ }
116
+
117
+ return h(node, 'table', inner);
118
+ };
119
+ }
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Copyright 2022 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import fromMarkdown from './from-markdown.js';
13
+ import toMarkdown from './to-markdown.js';
14
+ import syntax from './syntax.js';
15
+
16
+ export default function remarkPlugin(options = {}) {
17
+ const data = this.data();
18
+
19
+ function add(field, value) {
20
+ /* c8 ignore next 2 */
21
+ if (data[field]) {
22
+ data[field].push(value);
23
+ } else {
24
+ data[field] = [value];
25
+ }
26
+ }
27
+
28
+ const opts = {
29
+ processor: this,
30
+ ...options,
31
+ };
32
+
33
+ add('micromarkExtensions', syntax(options));
34
+ add('fromMarkdownExtensions', fromMarkdown(opts));
35
+ add('toMarkdownExtensions', toMarkdown(options));
36
+ }
@@ -0,0 +1,352 @@
1
+ /*
2
+ * Copyright 2022 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ /* eslint-disable no-use-before-define,no-underscore-dangle,no-param-reassign */
13
+ import { codes } from 'micromark-util-symbol/codes.js';
14
+ import { types } from 'micromark-util-symbol/types.js';
15
+ import {
16
+ markdownLineEnding,
17
+ markdownSpace,
18
+ } from 'micromark-util-character';
19
+ import {
20
+ TYPE_BODY, TYPE_CELL, TYPE_HEADER, TYPE_FOOTER, TYPE_TABLE,
21
+ } from './types.js';
22
+
23
+ // the cell divider: | or +
24
+ const TYPE_CELL_DIVIDER = 'cellDivider';
25
+
26
+ // a line within a row. can have cells or dividers or both, in case of row spans
27
+ const TYPE_ROW_LINE = 'rowLine';
28
+
29
+ // the grid divider: - / =
30
+ const TYPE_GRID_DIVIDER = 'gridDivider';
31
+
32
+ const V_ALIGN_CODES = {
33
+ [codes.lowercaseV]: 'bottom',
34
+ [codes.lowercaseX]: 'middle',
35
+ [codes.caret]: 'top',
36
+ };
37
+
38
+ function parse() {
39
+ return {
40
+ tokenize: tokenizeTable,
41
+ resolve: resolveTable,
42
+ resolveAll: resolveAllTable,
43
+ concrete: true,
44
+ };
45
+
46
+ function tokenizeTable(effects, ok, nok) {
47
+ // positions of columns
48
+ const cols = [0];
49
+ let numRows = 0;
50
+ let numCols = 0;
51
+ let colPos = 0;
52
+ let rowLine = null;
53
+ let align = '';
54
+ let valign = '';
55
+ return start;
56
+
57
+ function start(code) {
58
+ effects.enter(TYPE_TABLE)._cols = cols;
59
+ effects.enter(TYPE_BODY);
60
+ return lineStart(code);
61
+ }
62
+
63
+ function lineStart(code) {
64
+ if (code === codes.plusSign || code === codes.verticalBar) {
65
+ rowLine = effects.enter(TYPE_ROW_LINE);
66
+ effects.enter(TYPE_CELL_DIVIDER);
67
+ effects.consume(code);
68
+ effects.exit(TYPE_CELL_DIVIDER);
69
+ colPos = 0;
70
+ numCols = 0;
71
+ return cellOrGridStart;
72
+ }
73
+ if (numRows < 3) {
74
+ return nok(code);
75
+ }
76
+ effects.exit(TYPE_BODY);
77
+ effects.exit(TYPE_TABLE);
78
+ return ok(code);
79
+ }
80
+
81
+ function cellOrGridStart(code) {
82
+ align = '';
83
+ valign = '';
84
+ if (code === codes.dash || code === codes.equalsTo
85
+ || code === codes.colon || code === codes.greaterThan) {
86
+ effects.enter(TYPE_GRID_DIVIDER)._colStart = colPos;
87
+ colPos += 1;
88
+ if (code === codes.colon) {
89
+ align = 'left';
90
+ } else if (code === codes.greaterThan) {
91
+ align = 'justify';
92
+ }
93
+ effects.consume(code);
94
+ return gridDivider;
95
+ }
96
+
97
+ if (code === codes.eof || markdownLineEnding(code)) {
98
+ return lineEnd(code);
99
+ }
100
+
101
+ effects.enter(TYPE_CELL)._colStart = colPos;
102
+ colPos += 1;
103
+ effects.consume(code);
104
+
105
+ if (markdownSpace(code)) {
106
+ return cellSpace;
107
+ }
108
+ return cell;
109
+ }
110
+
111
+ function cellSpace(code) {
112
+ if (code === codes.eof || markdownLineEnding(code)) {
113
+ // mark as discarded, will be filtered out in transform
114
+ effects.exit(TYPE_CELL)._discard = true;
115
+ return lineEnd(code);
116
+ }
117
+ if (markdownSpace(code)) {
118
+ colPos += 1;
119
+ effects.consume(code);
120
+ return cellSpace;
121
+ }
122
+ return cell(code);
123
+ }
124
+
125
+ function lineEnd(code) {
126
+ if (numCols === 0) {
127
+ return nok(code);
128
+ }
129
+ if (markdownLineEnding(code)) {
130
+ effects.enter(types.lineEnding);
131
+ effects.consume(code);
132
+ effects.exit(types.lineEnding);
133
+ }
134
+ effects.exit(TYPE_ROW_LINE);
135
+ if (code === codes.eof) {
136
+ effects.exit(TYPE_BODY);
137
+ effects.exit(TYPE_TABLE);
138
+ return ok(code);
139
+ }
140
+ numRows += 1;
141
+ return lineStart;
142
+ }
143
+
144
+ function gridDivider(code) {
145
+ colPos += 1;
146
+ if (code === codes.dash || code === codes.equalsTo) {
147
+ if (!rowLine._type) {
148
+ rowLine._type = code;
149
+ }
150
+ effects.consume(code);
151
+ return gridDivider;
152
+ }
153
+ if (code === codes.colon) {
154
+ if (!align) {
155
+ align = 'right';
156
+ } else if (align === 'left') {
157
+ align = 'center';
158
+ } else {
159
+ return nok(code);
160
+ }
161
+ effects.consume(code);
162
+ return gridDividerEnd;
163
+ }
164
+ if (code === codes.lessThan) {
165
+ if (align !== 'justify') {
166
+ return nok(code);
167
+ }
168
+ effects.consume(code);
169
+ return gridDividerEnd;
170
+ }
171
+
172
+ if (V_ALIGN_CODES[code]) {
173
+ if (valign) {
174
+ return nok(code);
175
+ }
176
+ valign = V_ALIGN_CODES[code];
177
+ effects.consume(code);
178
+ return gridDivider;
179
+ }
180
+ if (code === codes.plusSign || code === codes.verticalBar) {
181
+ colPos -= 1;
182
+ return gridDividerEnd(code);
183
+ }
184
+ return nok(code);
185
+ }
186
+
187
+ function gridDividerEnd(code) {
188
+ if (code !== codes.plusSign && code !== codes.verticalBar) {
189
+ return nok(code);
190
+ }
191
+ // for a super small column, assume dash
192
+ if (!rowLine._type) {
193
+ rowLine._type = code.dash;
194
+ }
195
+ colPos += 1;
196
+ // remember cols
197
+ const idx = cols.indexOf(colPos);
198
+ if (idx < 0) {
199
+ cols.push(colPos);
200
+ cols.sort((c0, c1) => c0 - c1);
201
+ }
202
+ const token = effects.exit(TYPE_GRID_DIVIDER);
203
+ token._colEnd = colPos;
204
+ token._align = align;
205
+ token._valign = valign;
206
+ effects.enter(TYPE_CELL_DIVIDER);
207
+ effects.consume(code);
208
+ effects.exit(TYPE_CELL_DIVIDER);
209
+ numCols += 1;
210
+ return cellOrGridStart;
211
+ }
212
+
213
+ function cell(code) {
214
+ colPos += 1;
215
+ // find existing col
216
+ if (code === codes.verticalBar || code === codes.plusSign) {
217
+ const idx = cols.indexOf(colPos);
218
+ if (idx >= 0) {
219
+ effects.exit(TYPE_CELL)._colEnd = colPos;
220
+ effects.enter(TYPE_CELL_DIVIDER);
221
+ effects.consume(code);
222
+ effects.exit(TYPE_CELL_DIVIDER);
223
+ numCols += 1;
224
+ return cellOrGridStart;
225
+ }
226
+ effects.consume(code);
227
+ return cell;
228
+ }
229
+ if (code === codes.eof) {
230
+ // row with cells never terminate eof
231
+ return nok(code);
232
+ }
233
+
234
+ effects.consume(code);
235
+ return (code === codes.backslash)
236
+ ? cellEscaped
237
+ : cell;
238
+ }
239
+
240
+ function cellEscaped(code) {
241
+ if (code === codes.backslash || code === codes.verticalBar || code === codes.plusSign) {
242
+ colPos += 1;
243
+ effects.consume(code);
244
+ return cell;
245
+ }
246
+ return cell(code);
247
+ }
248
+ }
249
+
250
+ function resolveHeaderAndFooter(events, context) {
251
+ // detect headers:
252
+ // no `=` lines -> only body
253
+ // 1 `=` line -> header + body
254
+ // 2 `=` lines -> header + body + footer
255
+ const fatLines = [];
256
+ let bodyStart = -1; // should default to 1. but just be sure
257
+
258
+ for (let idx = 0; idx < events.length; idx += 1) {
259
+ const [e, node] = events[idx];
260
+ const { type } = node;
261
+ if (type === TYPE_BODY) {
262
+ if (e === 'enter') {
263
+ bodyStart = idx;
264
+ } else {
265
+ // eslint-disable-next-line prefer-const
266
+ let [hdrIdx, ftrIdx] = fatLines;
267
+ const bdy = node;
268
+ if (hdrIdx > bodyStart + 1) {
269
+ // insert header above body
270
+ const hdr = {
271
+ type: TYPE_HEADER,
272
+ start: bdy.start,
273
+ end: events[hdrIdx][1].end,
274
+ };
275
+ bdy.start = hdr.end;
276
+ events[bodyStart][1] = hdr;
277
+ events.splice(
278
+ hdrIdx,
279
+ 0,
280
+ ['exit', hdr, context],
281
+ ['enter', bdy, context],
282
+ );
283
+ idx += 2;
284
+ ftrIdx += 2;
285
+ }
286
+
287
+ if (ftrIdx) {
288
+ // insert footer below body
289
+ const ftr = {
290
+ type: TYPE_FOOTER,
291
+ start: events[ftrIdx][1].start,
292
+ end: bdy.end,
293
+ };
294
+ bdy.end = ftr.start;
295
+ events.splice(
296
+ ftrIdx,
297
+ 0,
298
+ ['exit', bdy, context],
299
+ ['enter', ftr, context],
300
+ );
301
+ idx += 2;
302
+ events[idx][1] = ftr;
303
+ }
304
+ }
305
+ } else if (type === TYPE_ROW_LINE && e === 'enter' && node._type === codes.equalsTo) {
306
+ fatLines.push(idx);
307
+ }
308
+ }
309
+ return events;
310
+ }
311
+
312
+ function resolveTable(events, context) {
313
+ // remove discarded
314
+ events = events.filter(([, node]) => !node._discard);
315
+
316
+ events = resolveHeaderAndFooter(events, context);
317
+ // let i = 0;
318
+ // for (const [d, { type }] of events) {
319
+ // if (d === 'exit') {
320
+ // i -= 2;
321
+ // }
322
+ // console.log(' '.repeat(i), d, type);
323
+ // if (d === 'enter') {
324
+ // i += 2;
325
+ // }
326
+ // }
327
+ return events;
328
+ }
329
+
330
+ function resolveAllTable(events, context) {
331
+ // since we create a detached parser for each cell content later (in from-markdown.js)
332
+ // we need to remember the definitions of the overall document. otherwise the cell parsers
333
+ // would not detect the image and link references.
334
+ const { defined } = context.parser;
335
+
336
+ // find all grid tables and remember the definitions
337
+ for (const [evt, node] of events) {
338
+ if (evt === 'enter' && node.type === TYPE_TABLE) {
339
+ node._definitions = defined;
340
+ }
341
+ }
342
+ return events;
343
+ }
344
+ }
345
+
346
+ export default function create(options = {}) {
347
+ return {
348
+ flow: {
349
+ [codes.plusSign]: parse(options),
350
+ },
351
+ };
352
+ }