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