1
+ //@ts -check
2
+ /**
3
+ * Original file licensed under MIT in
4
+ * https://github.com/electron/electronjs.org-new/blob/85c00545413ca5101955c0cf51f64150ae06e6e4/src/transformers/fiddle-embedder.js
5
+ */
6
+
7
+ const visitParents = require ( 'unist-util-visit-parents' ) ;
8
+ const path = require ( 'path' ) ;
9
+ const fs = require ( 'fs-extra' ) ;
10
+ const latestVersion = require ( 'latest-version' ) ;
11
+
12
+ let _version = '' ;
13
+ async function getVersion ( ) {
14
+ if ( _version === '' ) {
15
+ _version = await latestVersion ( 'electron' ) ;
16
+ }
17
+
18
+ return _version ;
19
+ }
20
+
21
+ module . exports = function attacher ( ) {
22
+ return transformer ;
23
+ } ;
24
+
25
+ /**
26
+ * Tests for AST nodes that match the following:
27
+ *
28
+ * 1) MDX import
29
+ *
30
+ * 2) Fiddle code block
31
+ * \```fiddle path/to/fiddle
32
+ *
33
+ * \```
34
+ * @param {import("unist").Node } node
35
+ * @returns boolean
36
+ */
37
+ function matchNode ( node ) {
38
+ return (
39
+ node . type === 'import' ||
40
+ ( node . type === 'code' && node . lang === 'fiddle' && ! ! node . meta )
41
+ ) ;
42
+ }
43
+
44
+ const importNode = {
45
+ type : 'import' ,
46
+ value :
47
+ "import Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n import LaunchButton from '@site/src/components/LaunchButton';" ,
48
+ } ;
49
+
50
+ /**
51
+ *
52
+ * @param {import("unist").Parent } tree
53
+ */
54
+ async function transformer ( tree ) {
55
+ let hasExistingImport = false ;
56
+ const version = await getVersion ( ) ;
57
+ visitParents ( tree , matchNode , visitor ) ;
58
+
59
+ if ( ! hasExistingImport ) {
60
+ tree . children . unshift ( importNode ) ;
61
+ }
62
+
63
+ /**
64
+ *
65
+ * @param {* } node
66
+ * @param {import("unist").Node[] } ancestors
67
+ * @returns { import("unist-util-visit-parents").ActionTuple }
68
+ */
69
+ function visitor ( node , ancestors ) {
70
+ if ( node . type === 'import' ) {
71
+ if ( node . value . includes ( '@theme/Tabs' ) ) {
72
+ hasExistingImport = true ;
73
+ }
74
+ return ;
75
+ }
76
+
77
+ const parent = ancestors [ 0 ] ;
78
+ // Supported formats are fiddle='<folder>|<option>|option'
79
+ // Options must be of the format key=value with no additional quotes.
80
+ const [ folder , ...others ] = node . meta . split ( '|' ) ;
81
+ const options = { } ;
82
+
83
+ // If there are optional parameters, parse them out to pass to the getFiddleAST method.
84
+ if ( others . length > 0 ) {
85
+ for ( const option of others ) {
86
+ // Use indexOf to support bizzare combinations like `|key=Myvalue=2` (which will properly
87
+ // parse to {'key': 'Myvalue=2'})
88
+ const firstEqual = option . indexOf ( '=' ) ;
89
+ const key = option . substr ( 0 , firstEqual ) ;
90
+ const value = option . substr ( firstEqual + 1 ) ;
91
+ options [ key ] = value ;
92
+ }
93
+ }
94
+
95
+ // Find where the Fiddle code block is relative to the parent,
96
+ // and splice the children array to insert the embedded Fiddle
97
+ if ( Array . isArray ( parent . children ) ) {
98
+ const index = parent . children . indexOf ( node ) ;
99
+ const newChildren = getFiddleAST ( folder , version , options ) ;
100
+ parent . children . splice ( index , 1 , ...newChildren ) ;
101
+ // Return an ActionTuple [Action, Index], where
102
+ // Action SKIP means we want to skip visiting these new children
103
+ // Index is the index of the AST we want to continue parsing at.
104
+ return [ visitParents . SKIP , index + newChildren . length ] ;
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * From a directory in `/docs/fiddles/`, generate the AST needed
110
+ * for the tabbed code MDX structure.
111
+ * @param {string } dir
112
+ * @param {string } version
113
+ */
114
+ function getFiddleAST ( dir , version , { focus = 'main.js' } ) {
115
+ const files = { } ;
116
+ const children = [ ] ;
117
+
118
+ // TODO: non-alphabetic sort
119
+ const fileNames = fs . readdirSync ( dir ) ;
120
+
121
+ if ( fileNames . length === 0 ) {
122
+ return children ;
123
+ }
124
+
125
+ if ( ! fileNames . includes ( focus ) ) {
126
+ throw new Error (
127
+ `Provided focus (${ focus } ) is not an available file in this fiddle (${ dir } ). Available files are [${ fileNames . join (
128
+ ', '
129
+ ) } ]`
130
+ ) ;
131
+ }
132
+
133
+ for ( const file of fileNames ) {
134
+ files [ file ] = fs . readFileSync ( path . join ( dir , file ) ) . toString ( ) ;
135
+ }
136
+
137
+ const tabValues = fileNames . reduce ( ( acc , val ) => {
138
+ return ( acc += `{ label: '${ val } ', value: '${ val } ', },` ) ;
139
+ } , '' ) ;
140
+
141
+ let index = 0 ;
142
+
143
+ // Generate MDXAST structure by iterating through all files in
144
+ // the folder and creating <TabItem> components and code blocks
145
+ // for each, and bookending those with the <Tabs> component.
146
+
147
+ // The finished product should look something like:
148
+ // <Tabs defaultValue="id1"
149
+ // values={[
150
+ // {label: 'id1', value: 'id1'},
151
+ // {label: 'id2', value: 'id2'}
152
+ // ]}>
153
+ // <TabItem value="id1">
154
+ // ```js
155
+ // const cow = 'say';
156
+ // ```
157
+ // <TabItem>
158
+ // <TabItem value="id2">
159
+ // ```js
160
+ // const hello = 'world';
161
+ // ```
162
+ // <TabItem>
163
+ // <Tabs>
164
+ children . push ( {
165
+ type : 'jsx' ,
166
+ value :
167
+ `<Tabs defaultValue="${ focus } " ` +
168
+ `values={[${ tabValues } ]}>
169
+ <TabItem value="${ fileNames [ index ] } ">` ,
170
+ } ) ;
171
+
172
+ while ( index < fileNames . length ) {
173
+ children . push ( {
174
+ type : 'code' ,
175
+ lang : path . extname ( fileNames [ index ] ) . slice ( 1 ) ,
176
+ value : files [ fileNames [ index ] ] ,
177
+ } ) ;
178
+
179
+ index ++ ;
180
+
181
+ if ( index < fileNames . length ) {
182
+ children . push ( {
183
+ type : 'jsx' ,
184
+ value : `</TabItem>\n<TabItem value="${ fileNames [ index ] } ">` ,
185
+ } ) ;
186
+ } else {
187
+ children . push (
188
+ {
189
+ type : 'jsx' ,
190
+ value : `</TabItem>\n</Tabs>` ,
191
+ } ,
192
+ {
193
+ type : 'jsx' ,
194
+ value : `<LaunchButton url="https://fiddle.electronjs.org/launch?target=electron/v${ version } /${ dir . replace (
195
+ 'latest/' ,
196
+ ''
197
+ ) } "/>`,
198
+ }
199
+ ) ;
200
+ }
201
+ }
202
+
203
+ return children ;
204
+ }
0 commit comments