11/**
2- * @import {Directives, LeafDirective, TextDirective} from 'mdast-util-directive'
2+ * @import {Directives, LeafDirective, TextDirective, ToMarkdownOptions } from 'mdast-util-directive'
33 * @import {
44 * CompileContext,
55 * Extension as FromMarkdownExtension,
@@ -22,9 +22,10 @@ import {visitParents} from 'unist-util-visit-parents'
2222
2323const own = { } . hasOwnProperty
2424
25- const shortcut = / ^ [ ^ \t \n \r " # ' . < = > ` } ] + $ /
25+ /** @type {Readonly<ToMarkdownOptions> } */
26+ const emptyOptions = { }
2627
27- handleDirective . peek = peekDirective
28+ const shortcut = / ^ [ ^ \t \n \r " # ' . < = > ` } ] + $ /
2829
2930/**
3031 * Create an extension for `mdast-util-from-markdown` to enable directives in
@@ -80,10 +81,16 @@ export function directiveFromMarkdown() {
8081 * Create an extension for `mdast-util-to-markdown` to enable directives in
8182 * markdown.
8283 *
84+ * @param {Readonly<ToMarkdownOptions> | null | undefined } [options]
85+ * Configuration (optional).
8386 * @returns {ToMarkdownExtension }
8487 * Extension for `mdast-util-to-markdown` to enable directives.
8588 */
86- export function directiveToMarkdown ( ) {
89+ export function directiveToMarkdown ( options ) {
90+ const settings = options || emptyOptions
91+
92+ handleDirective . peek = peekDirective
93+
8794 return {
8895 handlers : {
8996 containerDirective : handleDirective ,
@@ -108,6 +115,156 @@ export function directiveToMarkdown() {
108115 { atBreak : true , character : ':' , after : ':' }
109116 ]
110117 }
118+
119+ /**
120+ * @type {ToMarkdownHandle }
121+ * @param {Directives } node
122+ */
123+ function handleDirective ( node , _ , state , info ) {
124+ const tracker = state . createTracker ( info )
125+ const sequence = fence ( node )
126+ const exit = state . enter ( node . type )
127+ let value = tracker . move ( sequence + ( node . name || '' ) )
128+ /** @type {LeafDirective | Paragraph | TextDirective | undefined } */
129+ let label
130+
131+ if ( node . type === 'containerDirective' ) {
132+ const head = ( node . children || [ ] ) [ 0 ]
133+ label = inlineDirectiveLabel ( head ) ? head : undefined
134+ } else {
135+ label = node
136+ }
137+
138+ if ( label && label . children && label . children . length > 0 ) {
139+ const exit = state . enter ( 'label' )
140+ /** @type {ConstructName } */
141+ const labelType = `${ node . type } Label`
142+ const subexit = state . enter ( labelType )
143+ value += tracker . move ( '[' )
144+ value += tracker . move (
145+ state . containerPhrasing ( label , {
146+ ...tracker . current ( ) ,
147+ before : value ,
148+ after : ']'
149+ } )
150+ )
151+ value += tracker . move ( ']' )
152+ subexit ( )
153+ exit ( )
154+ }
155+
156+ value += tracker . move ( attributes ( node , state ) )
157+
158+ if ( node . type === 'containerDirective' ) {
159+ const head = ( node . children || [ ] ) [ 0 ]
160+ let shallow = node
161+
162+ if ( inlineDirectiveLabel ( head ) ) {
163+ shallow = Object . assign ( { } , node , { children : node . children . slice ( 1 ) } )
164+ }
165+
166+ if ( shallow && shallow . children && shallow . children . length > 0 ) {
167+ value += tracker . move ( '\n' )
168+ value += tracker . move ( state . containerFlow ( shallow , tracker . current ( ) ) )
169+ }
170+
171+ value += tracker . move ( '\n' + sequence )
172+ }
173+
174+ exit ( )
175+ return value
176+ }
177+
178+ /**
179+ * @param {Directives } node
180+ * @param {State } state
181+ * @returns {string }
182+ */
183+ function attributes ( node , state ) {
184+ // If the alternative is less common than `quote`, switch.
185+ const appliedQuote = settings . quote || state . options . quote || '"'
186+ const subset =
187+ node . type === 'textDirective'
188+ ? [ appliedQuote ]
189+ : [ appliedQuote , '\n' , '\r' ]
190+ const attributes = node . attributes || { }
191+ /** @type {Array<string> } */
192+ const values = [ ]
193+ /** @type {string | undefined } */
194+ let classesFull
195+ /** @type {string | undefined } */
196+ let classes
197+ /** @type {string | undefined } */
198+ let id
199+ /** @type {string } */
200+ let key
201+
202+ for ( key in attributes ) {
203+ if (
204+ own . call ( attributes , key ) &&
205+ attributes [ key ] !== undefined &&
206+ attributes [ key ] !== null
207+ ) {
208+ const value = String ( attributes [ key ] )
209+
210+ if ( key === 'id' ) {
211+ id = shortcut . test ( value ) ? '#' + value : quoted ( 'id' , value )
212+ } else if ( key === 'class' ) {
213+ const list = value . split ( / [ \t \n \r ] + / g)
214+ /** @type {Array<string> } */
215+ const classesFullList = [ ]
216+ /** @type {Array<string> } */
217+ const classesList = [ ]
218+ let index = - 1
219+
220+ while ( ++ index < list . length ) {
221+ ; ( shortcut . test ( list [ index ] ) ? classesList : classesFullList ) . push (
222+ list [ index ]
223+ )
224+ }
225+
226+ classesFull =
227+ classesFullList . length > 0
228+ ? quoted ( 'class' , classesFullList . join ( ' ' ) )
229+ : ''
230+ classes = classesList . length > 0 ? '.' + classesList . join ( '.' ) : ''
231+ } else {
232+ values . push ( quoted ( key , value ) )
233+ }
234+ }
235+ }
236+
237+ if ( classesFull ) {
238+ values . unshift ( classesFull )
239+ }
240+
241+ if ( classes ) {
242+ values . unshift ( classes )
243+ }
244+
245+ if ( id ) {
246+ values . unshift ( id )
247+ }
248+
249+ return values . length > 0 ? '{' + values . join ( ' ' ) + '}' : ''
250+
251+ /**
252+ * @param {string } key
253+ * @param {string } value
254+ * @returns {string }
255+ */
256+ function quoted ( key , value ) {
257+ return (
258+ key +
259+ ( value
260+ ? '=' +
261+ appliedQuote +
262+ stringifyEntitiesLight ( value , { subset} ) +
263+ appliedQuote
264+ : '' )
265+ )
266+ }
267+ }
111268}
112269
113270/**
@@ -276,154 +433,11 @@ function exit(token) {
276433 this . exit ( token )
277434}
278435
279- /**
280- * @type {ToMarkdownHandle }
281- * @param {Directives } node
282- */
283- function handleDirective ( node , _ , state , info ) {
284- const tracker = state . createTracker ( info )
285- const sequence = fence ( node )
286- const exit = state . enter ( node . type )
287- let value = tracker . move ( sequence + ( node . name || '' ) )
288- /** @type {LeafDirective | Paragraph | TextDirective | undefined } */
289- let label
290-
291- if ( node . type === 'containerDirective' ) {
292- const head = ( node . children || [ ] ) [ 0 ]
293- label = inlineDirectiveLabel ( head ) ? head : undefined
294- } else {
295- label = node
296- }
297-
298- if ( label && label . children && label . children . length > 0 ) {
299- const exit = state . enter ( 'label' )
300- /** @type {ConstructName } */
301- const labelType = `${ node . type } Label`
302- const subexit = state . enter ( labelType )
303- value += tracker . move ( '[' )
304- value += tracker . move (
305- state . containerPhrasing ( label , {
306- ...tracker . current ( ) ,
307- before : value ,
308- after : ']'
309- } )
310- )
311- value += tracker . move ( ']' )
312- subexit ( )
313- exit ( )
314- }
315-
316- value += tracker . move ( attributes ( node , state ) )
317-
318- if ( node . type === 'containerDirective' ) {
319- const head = ( node . children || [ ] ) [ 0 ]
320- let shallow = node
321-
322- if ( inlineDirectiveLabel ( head ) ) {
323- shallow = Object . assign ( { } , node , { children : node . children . slice ( 1 ) } )
324- }
325-
326- if ( shallow && shallow . children && shallow . children . length > 0 ) {
327- value += tracker . move ( '\n' )
328- value += tracker . move ( state . containerFlow ( shallow , tracker . current ( ) ) )
329- }
330-
331- value += tracker . move ( '\n' + sequence )
332- }
333-
334- exit ( )
335- return value
336- }
337-
338436/** @type {ToMarkdownHandle } */
339437function peekDirective ( ) {
340438 return ':'
341439}
342440
343- /**
344- * @param {Directives } node
345- * @param {State } state
346- * @returns {string }
347- */
348- function attributes ( node , state ) {
349- const quote = state . options . quote || '"'
350- const subset = node . type === 'textDirective' ? [ quote ] : [ quote , '\n' , '\r' ]
351- const attributes = node . attributes || { }
352- /** @type {Array<string> } */
353- const values = [ ]
354- /** @type {string | undefined } */
355- let classesFull
356- /** @type {string | undefined } */
357- let classes
358- /** @type {string | undefined } */
359- let id
360- /** @type {string } */
361- let key
362-
363- for ( key in attributes ) {
364- if (
365- own . call ( attributes , key ) &&
366- attributes [ key ] !== undefined &&
367- attributes [ key ] !== null
368- ) {
369- const value = String ( attributes [ key ] )
370-
371- if ( key === 'id' ) {
372- id = shortcut . test ( value ) ? '#' + value : quoted ( 'id' , value )
373- } else if ( key === 'class' ) {
374- const list = value . split ( / [ \t \n \r ] + / g)
375- /** @type {Array<string> } */
376- const classesFullList = [ ]
377- /** @type {Array<string> } */
378- const classesList = [ ]
379- let index = - 1
380-
381- while ( ++ index < list . length ) {
382- ; ( shortcut . test ( list [ index ] ) ? classesList : classesFullList ) . push (
383- list [ index ]
384- )
385- }
386-
387- classesFull =
388- classesFullList . length > 0
389- ? quoted ( 'class' , classesFullList . join ( ' ' ) )
390- : ''
391- classes = classesList . length > 0 ? '.' + classesList . join ( '.' ) : ''
392- } else {
393- values . push ( quoted ( key , value ) )
394- }
395- }
396- }
397-
398- if ( classesFull ) {
399- values . unshift ( classesFull )
400- }
401-
402- if ( classes ) {
403- values . unshift ( classes )
404- }
405-
406- if ( id ) {
407- values . unshift ( id )
408- }
409-
410- return values . length > 0 ? '{' + values . join ( ' ' ) + '}' : ''
411-
412- /**
413- * @param {string } key
414- * @param {string } value
415- * @returns {string }
416- */
417- function quoted ( key , value ) {
418- return (
419- key +
420- ( value
421- ? '=' + quote + stringifyEntitiesLight ( value , { subset} ) + quote
422- : '' )
423- )
424- }
425- }
426-
427441/**
428442 * @param {Nodes } node
429443 * @returns {node is Paragraph & {data: {directiveLabel: true}} }
0 commit comments