@san-siva/blogkit-md 0.2.9 → 0.3.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.
- package/README.md +20 -9
- package/dist/index.js +52 -42
- package/package.json +2 -2
- package/src/utils/groupSections.test.ts +8 -2
- package/src/utils/parseMarkdown.test.ts +39 -0
- package/src/utils/parseMarkdown.ts +16 -1
- package/src/utils/renderMarkdown.tsx +33 -33
package/README.md
CHANGED
|
@@ -79,13 +79,12 @@ description: A short description shown below the title
|
|
|
79
79
|
| Image | `` |
|
|
80
80
|
| Ordered list | `1. item` |
|
|
81
81
|
| Unordered list | `- item` |
|
|
82
|
+
| Task list | `- [ ] item` / `- [x] item` |
|
|
82
83
|
| Table | `\| col \| col \|` — headers and rows only |
|
|
83
84
|
| Code block | ` ```lang ` |
|
|
84
85
|
| Mermaid diagram | ` ```mermaid ` |
|
|
85
86
|
| Thematic break | `---` |
|
|
86
|
-
| Blockquote
|
|
87
|
-
| Blockquote (warning) | `> ~text` — renders as warning callout |
|
|
88
|
-
| Blockquote (error) | `> !text` — renders as error callout |
|
|
87
|
+
| Blockquote / Callout | `> text` or `> [!TYPE]` — renders as callout |
|
|
89
88
|
|
|
90
89
|
## Philosophy
|
|
91
90
|
|
|
@@ -215,15 +214,23 @@ Result content.
|
|
|
215
214
|
|
|
216
215
|
### Callouts
|
|
217
216
|
|
|
218
|
-
Blockquotes are rendered as styled callout banners.
|
|
217
|
+
Blockquotes are rendered as styled callout banners. Plain blockquotes render as info callouts. Use GitHub-style alert syntax to control the type:
|
|
219
218
|
|
|
220
|
-
|
|
219
|
+
| Syntax | Callout type |
|
|
220
|
+
| :------------- | :----------- |
|
|
221
|
+
| `[!NOTE]` | info |
|
|
222
|
+
| `[!TIP]` | info |
|
|
223
|
+
| `[!IMPORTANT]` | info |
|
|
224
|
+
| `[!WARNING]` | warning |
|
|
225
|
+
| `[!CAUTION]` | error |
|
|
221
226
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
>
|
|
227
|
+
```markdown
|
|
228
|
+
> [!WARNING]
|
|
229
|
+
>
|
|
230
|
+
> Do not clone this repository into system folders.
|
|
231
|
+
```
|
|
225
232
|
|
|
226
|
-
The
|
|
233
|
+
The `[!TYPE]` line is stripped from the rendered output.
|
|
227
234
|
|
|
228
235
|
## Want more customization?
|
|
229
236
|
|
|
@@ -238,3 +245,7 @@ Contributions are welcome!
|
|
|
238
245
|
|
|
239
246
|
- **Author:** [Santhosh Siva](https://www.santhoshsiva.dev)
|
|
240
247
|
- **License:** [MIT](https://github.com/san-siva/blogkit-md/blob/main/LICENSE)
|
|
248
|
+
|
|
249
|
+
> [!WARNING]
|
|
250
|
+
>
|
|
251
|
+
> This project is in early development.
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,25 @@ import remarkGfm from "remark-gfm";
|
|
|
29
29
|
import remarkParse from "remark-parse";
|
|
30
30
|
import { unified } from "unified";
|
|
31
31
|
import { parse as parseYaml } from "yaml";
|
|
32
|
+
|
|
33
|
+
// src/utils/extractText.ts
|
|
34
|
+
var extractText = (nodes) => nodes.map((node) => {
|
|
35
|
+
if (node.type === "text") return node.value;
|
|
36
|
+
if ("children" in node)
|
|
37
|
+
return extractText(node.children);
|
|
38
|
+
return "";
|
|
39
|
+
}).join("");
|
|
40
|
+
|
|
41
|
+
// src/utils/parseMarkdown.ts
|
|
42
|
+
var consumeH1Title = (ast, frontmatter) => {
|
|
43
|
+
var _a;
|
|
44
|
+
if (!frontmatter.title && ((_a = ast.children[0]) == null ? void 0 : _a.type) === "heading" && ast.children[0].depth === 1) {
|
|
45
|
+
const title = extractText(ast.children[0].children);
|
|
46
|
+
ast.children.shift();
|
|
47
|
+
return __spreadProps(__spreadValues({}, frontmatter), { title });
|
|
48
|
+
}
|
|
49
|
+
return frontmatter;
|
|
50
|
+
};
|
|
32
51
|
var parseMarkdown = (content) => {
|
|
33
52
|
var _a, _b;
|
|
34
53
|
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ["yaml"]);
|
|
@@ -44,6 +63,7 @@ var parseMarkdown = (content) => {
|
|
|
44
63
|
};
|
|
45
64
|
ast.children.shift();
|
|
46
65
|
}
|
|
66
|
+
frontmatter = consumeH1Title(ast, frontmatter);
|
|
47
67
|
return { ast, frontmatter };
|
|
48
68
|
};
|
|
49
69
|
|
|
@@ -51,19 +71,12 @@ var parseMarkdown = (content) => {
|
|
|
51
71
|
import {
|
|
52
72
|
BlogSection,
|
|
53
73
|
Callout,
|
|
74
|
+
CheckList,
|
|
54
75
|
CodeBlock,
|
|
55
76
|
Mermaid,
|
|
56
77
|
Table
|
|
57
78
|
} from "@san-siva/blogkit";
|
|
58
79
|
|
|
59
|
-
// src/utils/extractText.ts
|
|
60
|
-
var extractText = (nodes) => nodes.map((node) => {
|
|
61
|
-
if (node.type === "text") return node.value;
|
|
62
|
-
if ("children" in node)
|
|
63
|
-
return extractText(node.children);
|
|
64
|
-
return "";
|
|
65
|
-
}).join("");
|
|
66
|
-
|
|
67
80
|
// src/utils/groupSections.ts
|
|
68
81
|
var consumeNode = (nodes, index, sections, section) => {
|
|
69
82
|
const node = nodes.at(index);
|
|
@@ -216,7 +229,8 @@ function renderNode({
|
|
|
216
229
|
key,
|
|
217
230
|
nextNode,
|
|
218
231
|
inList = false,
|
|
219
|
-
inCallout = false
|
|
232
|
+
inCallout = false,
|
|
233
|
+
counters
|
|
220
234
|
}) {
|
|
221
235
|
var _a;
|
|
222
236
|
switch (node.type) {
|
|
@@ -231,10 +245,11 @@ function renderNode({
|
|
|
231
245
|
}
|
|
232
246
|
case "code": {
|
|
233
247
|
if (node.lang === "mermaid") {
|
|
248
|
+
const mermaidId = counters.mermaid++;
|
|
234
249
|
return /* @__PURE__ */ jsx2(
|
|
235
250
|
Mermaid,
|
|
236
251
|
{
|
|
237
|
-
id: `mermaid-${
|
|
252
|
+
id: `mermaid-${mermaidId}`,
|
|
238
253
|
code: node.value,
|
|
239
254
|
hasMarginUp: true,
|
|
240
255
|
hasMarginDown: true
|
|
@@ -286,30 +301,11 @@ function renderNode({
|
|
|
286
301
|
if ((firstChild == null ? void 0 : firstChild.type) === "paragraph") {
|
|
287
302
|
const firstInline = firstChild.children[0];
|
|
288
303
|
if ((firstInline == null ? void 0 : firstInline.type) === "text") {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
children: [
|
|
295
|
-
__spreadProps(__spreadValues({}, firstInline), { value: trimmed }),
|
|
296
|
-
...firstChild.children.slice(1)
|
|
297
|
-
]
|
|
298
|
-
}),
|
|
299
|
-
...children.slice(1)
|
|
300
|
-
];
|
|
301
|
-
} else if (firstInline.value.startsWith("~")) {
|
|
302
|
-
calloutType = "warning";
|
|
303
|
-
const trimmed = firstInline.value.slice(1).trimStart();
|
|
304
|
-
strippedChildren = [
|
|
305
|
-
__spreadProps(__spreadValues({}, firstChild), {
|
|
306
|
-
children: [
|
|
307
|
-
__spreadProps(__spreadValues({}, firstInline), { value: trimmed }),
|
|
308
|
-
...firstChild.children.slice(1)
|
|
309
|
-
]
|
|
310
|
-
}),
|
|
311
|
-
...children.slice(1)
|
|
312
|
-
];
|
|
304
|
+
const githubAlertMatch = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]$/i.exec(firstInline.value);
|
|
305
|
+
if (githubAlertMatch) {
|
|
306
|
+
const alertType = githubAlertMatch[1].toUpperCase();
|
|
307
|
+
calloutType = alertType === "WARNING" ? "warning" : alertType === "CAUTION" ? "error" : "info";
|
|
308
|
+
strippedChildren = children.slice(1);
|
|
313
309
|
}
|
|
314
310
|
}
|
|
315
311
|
}
|
|
@@ -318,17 +314,30 @@ function renderNode({
|
|
|
318
314
|
node: child,
|
|
319
315
|
key: index,
|
|
320
316
|
nextNode: strippedChildren[index + 1],
|
|
321
|
-
inCallout: true
|
|
317
|
+
inCallout: true,
|
|
318
|
+
counters
|
|
322
319
|
})
|
|
323
320
|
) }, key);
|
|
324
321
|
}
|
|
325
322
|
case "list": {
|
|
323
|
+
const isTaskList = node.children.every((item) => item.checked !== null && item.checked !== void 0);
|
|
324
|
+
if (isTaskList) {
|
|
325
|
+
const items = node.children.map((item, index) => ({
|
|
326
|
+
id: String(index),
|
|
327
|
+
isChecked: item.checked === true,
|
|
328
|
+
children: item.children.map(
|
|
329
|
+
(child, childIndex) => renderNode({ node: child, key: childIndex, inList: true, counters })
|
|
330
|
+
)
|
|
331
|
+
}));
|
|
332
|
+
return /* @__PURE__ */ jsx2(CheckList, { items, hasMarginUp: true, hasMarginDown: true }, key);
|
|
333
|
+
}
|
|
326
334
|
const Tag = node.ordered ? "ol" : "ul";
|
|
327
335
|
return /* @__PURE__ */ jsx2(Tag, { className: styles2["margin-bottom--2"], children: node.children.map((item, index) => /* @__PURE__ */ jsx2("li", { children: item.children.map(
|
|
328
336
|
(child, index2) => renderNode({
|
|
329
337
|
node: child,
|
|
330
338
|
key: index2,
|
|
331
|
-
inList: true
|
|
339
|
+
inList: true,
|
|
340
|
+
counters
|
|
332
341
|
})
|
|
333
342
|
) }, index)) }, key);
|
|
334
343
|
}
|
|
@@ -337,24 +346,25 @@ function renderNode({
|
|
|
337
346
|
}
|
|
338
347
|
}
|
|
339
348
|
}
|
|
340
|
-
function renderNodes(nodes) {
|
|
349
|
+
function renderNodes(nodes, counters) {
|
|
341
350
|
return nodes.map(
|
|
342
|
-
(node, index) => renderNode({ node, key: index, nextNode: nodes[index + 1] })
|
|
351
|
+
(node, index) => renderNode({ node, key: index, nextNode: nodes[index + 1], counters })
|
|
343
352
|
);
|
|
344
353
|
}
|
|
345
|
-
function renderSection(section, key = -1) {
|
|
354
|
+
function renderSection(section, counters, key = -1) {
|
|
346
355
|
var _a;
|
|
347
356
|
return /* @__PURE__ */ jsxs(BlogSection, { title: (_a = section == null ? void 0 : section.title) != null ? _a : "", children: [
|
|
348
|
-
renderNodes(section.nodes),
|
|
357
|
+
renderNodes(section.nodes, counters),
|
|
349
358
|
section.subsections.map(
|
|
350
|
-
(subsection, index) => renderSection(subsection, index)
|
|
359
|
+
(subsection, index) => renderSection(subsection, counters, index)
|
|
351
360
|
)
|
|
352
361
|
] }, key);
|
|
353
362
|
}
|
|
354
363
|
var renderMarkdownAst = (ast) => {
|
|
364
|
+
const counters = { mermaid: 0 };
|
|
355
365
|
const grouped = groupSections(ast.children);
|
|
356
366
|
return {
|
|
357
|
-
sections: grouped.map((section, index) => renderSection(section, index))
|
|
367
|
+
sections: grouped.map((section, index) => renderSection(section, counters, index))
|
|
358
368
|
};
|
|
359
369
|
};
|
|
360
370
|
var MarkdownSections = ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@san-siva/blogkit-md",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Converts markdown files into JSX blog posts for Blogkit",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"fix": "eslint . --config eslint.config.ts --fix"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@san-siva/blogkit": "^1.1.
|
|
24
|
+
"@san-siva/blogkit": "^1.1.26",
|
|
25
25
|
"@san-siva/stylekit": "^1.0.12",
|
|
26
26
|
"next": "^16.0.10",
|
|
27
27
|
"react": "^19.0.0",
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import remarkGfm from 'remark-gfm';
|
|
3
|
+
import remarkParse from 'remark-parse';
|
|
4
|
+
import { unified } from 'unified';
|
|
5
|
+
import type { Root } from 'mdast';
|
|
2
6
|
|
|
3
|
-
import { parseMarkdown } from './parseMarkdown';
|
|
4
7
|
import { groupSections } from './groupSections';
|
|
5
8
|
|
|
6
|
-
const sections = (md: string) =>
|
|
9
|
+
const sections = (md: string) => {
|
|
10
|
+
const ast = unified().use(remarkParse).use(remarkGfm).parse(md) as Root;
|
|
11
|
+
return groupSections(ast.children);
|
|
12
|
+
};
|
|
7
13
|
|
|
8
14
|
describe('groupSections', () => {
|
|
9
15
|
describe('empty input', () => {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { parseMarkdown } from './parseMarkdown';
|
|
4
|
+
|
|
5
|
+
describe('parseMarkdown', () => {
|
|
6
|
+
describe('consumeH1Title', () => {
|
|
7
|
+
it('extracts a leading H1 as the title when no frontmatter title is set', () => {
|
|
8
|
+
const { frontmatter, ast } = parseMarkdown('# My Title\n\nSome content');
|
|
9
|
+
expect(frontmatter.title).toBe('My Title');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('removes the H1 from the AST after extracting it as the title', () => {
|
|
13
|
+
const { ast } = parseMarkdown('# My Title\n\nSome content');
|
|
14
|
+
expect(ast.children[0]?.type).not.toBe('heading');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('does not consume the H1 when frontmatter title is already set', () => {
|
|
18
|
+
const { frontmatter, ast } = parseMarkdown('---\ntitle: Frontmatter Title\n---\n\n# H1 Title');
|
|
19
|
+
expect(frontmatter.title).toBe('Frontmatter Title');
|
|
20
|
+
expect(ast.children[0]?.type).toBe('heading');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('does not extract a title when the first heading is not H1', () => {
|
|
24
|
+
const { frontmatter } = parseMarkdown('## Section Title');
|
|
25
|
+
expect(frontmatter.title).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('does not extract a title when H1 is not the first node', () => {
|
|
29
|
+
const { frontmatter, ast } = parseMarkdown('Some intro\n\n# Title');
|
|
30
|
+
expect(frontmatter.title).toBeUndefined();
|
|
31
|
+
expect(ast.children).toHaveLength(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('handles inline formatting in the H1', () => {
|
|
35
|
+
const { frontmatter } = parseMarkdown('# My **Bold** Title');
|
|
36
|
+
expect(frontmatter.title).toBe('My Bold Title');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { Root, Yaml } from 'mdast';
|
|
1
|
+
import type { Heading, Root, Yaml } from 'mdast';
|
|
2
2
|
import remarkFrontmatter from 'remark-frontmatter';
|
|
3
3
|
import remarkGfm from 'remark-gfm';
|
|
4
4
|
import remarkParse from 'remark-parse';
|
|
5
5
|
import { unified } from 'unified';
|
|
6
6
|
import { parse as parseYaml } from 'yaml';
|
|
7
7
|
|
|
8
|
+
import { extractText } from './extractText';
|
|
9
|
+
|
|
8
10
|
export type Frontmatter = {
|
|
9
11
|
title?: string;
|
|
10
12
|
description?: string;
|
|
@@ -15,6 +17,17 @@ export type ParseResult = {
|
|
|
15
17
|
frontmatter: Frontmatter;
|
|
16
18
|
};
|
|
17
19
|
|
|
20
|
+
// When a document has no frontmatter title, treat a leading H1 as the page title
|
|
21
|
+
// and remove it from the AST so it isn't also rendered as a section.
|
|
22
|
+
const consumeH1Title = (ast: Root, frontmatter: Frontmatter): Frontmatter => {
|
|
23
|
+
if (!frontmatter.title && ast.children[0]?.type === 'heading' && (ast.children[0] as Heading).depth === 1) {
|
|
24
|
+
const title = extractText((ast.children[0] as Heading).children);
|
|
25
|
+
ast.children.shift();
|
|
26
|
+
return { ...frontmatter, title };
|
|
27
|
+
}
|
|
28
|
+
return frontmatter;
|
|
29
|
+
};
|
|
30
|
+
|
|
18
31
|
export const parseMarkdown = (content: string): ParseResult => {
|
|
19
32
|
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ['yaml']);
|
|
20
33
|
const ast = processor.parse(content) as Root;
|
|
@@ -32,5 +45,7 @@ export const parseMarkdown = (content: string): ParseResult => {
|
|
|
32
45
|
ast.children.shift();
|
|
33
46
|
}
|
|
34
47
|
|
|
48
|
+
frontmatter = consumeH1Title(ast, frontmatter);
|
|
49
|
+
|
|
35
50
|
return { ast, frontmatter };
|
|
36
51
|
};
|
|
@@ -3,6 +3,8 @@ import React from 'react';
|
|
|
3
3
|
import {
|
|
4
4
|
BlogSection,
|
|
5
5
|
Callout,
|
|
6
|
+
CheckList,
|
|
7
|
+
type CheckListItem,
|
|
6
8
|
CodeBlock,
|
|
7
9
|
Mermaid,
|
|
8
10
|
Table,
|
|
@@ -15,18 +17,22 @@ import { renderPhrasingContent } from './renderPhrasingContent';
|
|
|
15
17
|
|
|
16
18
|
import styles from '@san-siva/stylekit/styles/index.module.scss';
|
|
17
19
|
|
|
20
|
+
type Counters = { mermaid: number };
|
|
21
|
+
|
|
18
22
|
function renderNode({
|
|
19
23
|
node,
|
|
20
24
|
key,
|
|
21
25
|
nextNode,
|
|
22
26
|
inList = false,
|
|
23
27
|
inCallout = false,
|
|
28
|
+
counters,
|
|
24
29
|
}: {
|
|
25
30
|
node: RootContent;
|
|
26
31
|
key: number;
|
|
27
32
|
nextNode?: RootContent;
|
|
28
33
|
inList?: boolean;
|
|
29
34
|
inCallout?: boolean;
|
|
35
|
+
counters: Counters;
|
|
30
36
|
}): React.ReactNode {
|
|
31
37
|
switch (node.type) {
|
|
32
38
|
case 'paragraph': {
|
|
@@ -51,10 +57,11 @@ function renderNode({
|
|
|
51
57
|
}
|
|
52
58
|
case 'code': {
|
|
53
59
|
if (node.lang === 'mermaid') {
|
|
60
|
+
const mermaidId = counters.mermaid++;
|
|
54
61
|
return (
|
|
55
62
|
<Mermaid
|
|
56
63
|
key={key}
|
|
57
|
-
id={`mermaid-${
|
|
64
|
+
id={`mermaid-${mermaidId}`}
|
|
58
65
|
code={node.value}
|
|
59
66
|
hasMarginUp
|
|
60
67
|
hasMarginDown
|
|
@@ -110,32 +117,11 @@ function renderNode({
|
|
|
110
117
|
if (firstChild?.type === 'paragraph') {
|
|
111
118
|
const firstInline = firstChild.children[0];
|
|
112
119
|
if (firstInline?.type === 'text') {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
...firstChild,
|
|
119
|
-
children: [
|
|
120
|
-
{ ...firstInline, value: trimmed },
|
|
121
|
-
...firstChild.children.slice(1),
|
|
122
|
-
],
|
|
123
|
-
},
|
|
124
|
-
...children.slice(1),
|
|
125
|
-
];
|
|
126
|
-
} else if (firstInline.value.startsWith('~')) {
|
|
127
|
-
calloutType = 'warning';
|
|
128
|
-
const trimmed = firstInline.value.slice(1).trimStart();
|
|
129
|
-
strippedChildren = [
|
|
130
|
-
{
|
|
131
|
-
...firstChild,
|
|
132
|
-
children: [
|
|
133
|
-
{ ...firstInline, value: trimmed },
|
|
134
|
-
...firstChild.children.slice(1),
|
|
135
|
-
],
|
|
136
|
-
},
|
|
137
|
-
...children.slice(1),
|
|
138
|
-
];
|
|
120
|
+
const githubAlertMatch = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]$/i.exec(firstInline.value);
|
|
121
|
+
if (githubAlertMatch) {
|
|
122
|
+
const alertType = githubAlertMatch[1].toUpperCase();
|
|
123
|
+
calloutType = alertType === 'WARNING' ? 'warning' : alertType === 'CAUTION' ? 'error' : 'info';
|
|
124
|
+
strippedChildren = children.slice(1);
|
|
139
125
|
}
|
|
140
126
|
}
|
|
141
127
|
}
|
|
@@ -148,12 +134,24 @@ function renderNode({
|
|
|
148
134
|
key: index,
|
|
149
135
|
nextNode: strippedChildren[index + 1],
|
|
150
136
|
inCallout: true,
|
|
137
|
+
counters,
|
|
151
138
|
})
|
|
152
139
|
)}
|
|
153
140
|
</Callout>
|
|
154
141
|
);
|
|
155
142
|
}
|
|
156
143
|
case 'list': {
|
|
144
|
+
const isTaskList = node.children.every(item => item.checked !== null && item.checked !== undefined);
|
|
145
|
+
if (isTaskList) {
|
|
146
|
+
const items: CheckListItem[] = node.children.map((item, index) => ({
|
|
147
|
+
id: String(index),
|
|
148
|
+
isChecked: item.checked === true,
|
|
149
|
+
children: item.children.map((child, childIndex) =>
|
|
150
|
+
renderNode({ node: child as RootContent, key: childIndex, inList: true, counters })
|
|
151
|
+
),
|
|
152
|
+
}));
|
|
153
|
+
return <CheckList key={key} items={items} hasMarginUp hasMarginDown />;
|
|
154
|
+
}
|
|
157
155
|
const Tag = node.ordered ? 'ol' : 'ul';
|
|
158
156
|
return (
|
|
159
157
|
<Tag key={key} className={styles['margin-bottom--2']}>
|
|
@@ -164,6 +162,7 @@ function renderNode({
|
|
|
164
162
|
node: child as RootContent,
|
|
165
163
|
key: index,
|
|
166
164
|
inList: true,
|
|
165
|
+
counters,
|
|
167
166
|
})
|
|
168
167
|
)}
|
|
169
168
|
</li>
|
|
@@ -177,18 +176,18 @@ function renderNode({
|
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
function renderNodes(nodes: RootContent[]): React.ReactNode[] {
|
|
179
|
+
function renderNodes(nodes: RootContent[], counters: Counters): React.ReactNode[] {
|
|
181
180
|
return nodes.map((node, index) =>
|
|
182
|
-
renderNode({ node, key: index, nextNode: nodes[index + 1] })
|
|
181
|
+
renderNode({ node, key: index, nextNode: nodes[index + 1], counters })
|
|
183
182
|
);
|
|
184
183
|
}
|
|
185
184
|
|
|
186
|
-
function renderSection(section: Section, key = -1): React.ReactNode {
|
|
185
|
+
function renderSection(section: Section, counters: Counters, key = -1): React.ReactNode {
|
|
187
186
|
return (
|
|
188
187
|
<BlogSection key={key} title={section?.title ?? ''}>
|
|
189
|
-
{renderNodes(section.nodes)}
|
|
188
|
+
{renderNodes(section.nodes, counters)}
|
|
190
189
|
{section.subsections.map((subsection, index) =>
|
|
191
|
-
renderSection(subsection, index)
|
|
190
|
+
renderSection(subsection, counters, index)
|
|
192
191
|
)}
|
|
193
192
|
</BlogSection>
|
|
194
193
|
);
|
|
@@ -199,9 +198,10 @@ export type RenderedMarkdown = {
|
|
|
199
198
|
};
|
|
200
199
|
|
|
201
200
|
export const renderMarkdownAst = (ast: Root): RenderedMarkdown => {
|
|
201
|
+
const counters: Counters = { mermaid: 0 };
|
|
202
202
|
const grouped = groupSections(ast.children);
|
|
203
203
|
return {
|
|
204
|
-
sections: grouped.map((section, index) => renderSection(section, index)),
|
|
204
|
+
sections: grouped.map((section, index) => renderSection(section, counters, index)),
|
|
205
205
|
};
|
|
206
206
|
};
|
|
207
207
|
|