-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathremark-code-tabs.js
113 lines (100 loc) · 2.97 KB
/
remark-code-tabs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import {visit} from 'unist-util-visit';
function getFullMeta(node) {
if (node.lang && node.meta) {
return node.lang + node.meta;
}
return node.lang || node.meta;
}
function fixLanguage(node) {
// Title can be something like this without spaces, let's fix it to have proper lang
// ```javascript{tabTitle: Angular 12+}{filename: main.ts}
// match everytung between ``` and {
const match = node.lang.match(/([^{]+)\{/);
// if we match a broken lang with no spacing, we fix it and also put the remainder in front of meta
if (match && match[1]) {
node.meta = `${node.lang.replace(match[1], '')}${node.meta}`;
node.lang = (match && match[1]) || node.lang;
}
return `${node.lang || ''}`;
}
function getFilename(node) {
const meta = getFullMeta(node);
const match = (meta || '').match(/\{filename:\s*([^}]+)\}/);
return (match && match[1]) || '';
}
function getTabTitle(node) {
const meta = getFullMeta(node);
const match = (meta || '').match(/\{tabTitle:\s*([^}]+)\}/);
return (match && match[1]) || '';
}
// TODO(dcramer): this should only operate on MDX
export default function remarkCodeTabs() {
return markdownAST => {
let lastParent = null;
let pendingCode = [];
let toRemove = [];
function flushPendingCode() {
if (pendingCode.length === 0) {
return;
}
const rootNode = pendingCode[0][0];
const children = pendingCode.reduce(
(arr, [node]) =>
arr.concat([
{
type: 'mdxJsxFlowElement',
name: 'CodeBlock',
attributes: [
{
type: 'mdxJsxAttribute',
name: 'language',
value: fixLanguage(node),
},
{type: 'mdxJsxAttribute', name: 'title', value: getTabTitle(node)},
{type: 'mdxJsxAttribute', name: 'filename', value: getFilename(node)},
],
children: [Object.assign({}, node)],
},
]),
[]
);
rootNode.type = 'element';
rootNode.data = {
hName: 'div',
hProperties: {
className: 'code-tabs-wrapper',
},
};
rootNode.children = [
{
type: 'mdxJsxFlowElement',
name: 'CodeTabs',
children,
},
];
toRemove = toRemove.concat(pendingCode.splice(1));
}
visit(
markdownAST,
() => true,
(node, _index, parent) => {
if (node.type !== 'code' || parent !== lastParent) {
flushPendingCode();
pendingCode = [];
lastParent = parent;
}
if (node.type === 'code') {
if (node.lang === null) {
node.lang = 'bash'; // bash is the default
}
pendingCode.push([node, parent]);
}
}
);
flushPendingCode();
toRemove.forEach(([node, parent]) => {
parent.children = parent.children.filter(n => n !== node);
});
return markdownAST;
};
}