@@ -18,8 +18,8 @@ import {
1818 kLinkCitations ,
1919 kQuartoTemplateParams ,
2020 kRelatedFormatsTitle ,
21+ kRelatedNotebooksTitle ,
2122 kSectionDivs ,
22- kTitle ,
2323 kTocDepth ,
2424 kTocLocation ,
2525} from "../../config/constants.ts" ;
@@ -75,6 +75,7 @@ import {
7575 isPdfOutput ,
7676 isPresentationOutput ,
7777} from "../../config/format.ts" ;
78+ import * as ld from "../../core/lodash.ts" ;
7879import { basename } from "path/mod.ts" ;
7980
8081export function formatPageLayout ( format : Format ) {
@@ -233,6 +234,9 @@ function bootstrapHtmlPostprocessor(
233234 renderedFormats : RenderedFormat [ ] ;
234235 } ,
235236 ) : Promise < HtmlPostProcessResult > => {
237+ // Resources used in this post processor
238+ const resources : string [ ] = [ ] ;
239+
236240 // use display-7 style for title
237241 const title = doc . querySelector ( "header > .title" ) ;
238242 if ( title ) {
@@ -314,78 +318,10 @@ function bootstrapHtmlPostprocessor(
314318
315319 // Inject links to other formats if there is another
316320 // format that of this file that has been rendered
317- if ( options . renderedFormats . length > 1 ) {
318- let dlLinkTarget = doc . querySelector ( `nav[role="doc-toc"]` ) ;
319- if ( dlLinkTarget === null ) {
320- dlLinkTarget = doc . querySelector ( "#quarto-margin-sidebar" ) ;
321- }
322-
323- if ( dlLinkTarget ) {
324- const bsIcon = ( format : Format ) => {
325- if ( isDocxOutput ( format . pandoc ) ) {
326- return "file-word" ;
327- } else if ( isPdfOutput ( format . pandoc ) ) {
328- return "file-pdf" ;
329- } else if ( isIpynbOutput ( format . pandoc ) ) {
330- return "journal-arrow-down" ;
331- } else if ( isMarkdownOutput ( format . pandoc ) ) {
332- return "file-code" ;
333- } else if ( isPresentationOutput ( format . pandoc ) ) {
334- return "file-slides" ;
335- } else {
336- return "file" ;
337- }
338- } ;
339-
340- const dlName = ( format : Format , path : string ) => {
341- if ( isIpynbOutput ( format . pandoc ) ) {
342- return basename ( path ) ;
343- } else {
344- return undefined ;
345- }
346- } ;
347-
348- const containerEl = doc . createElement ( "div" ) ;
349- containerEl . classList . add ( "quarto-alternate-formats" ) ;
350-
351- const heading = doc . createElement ( "h2" ) ;
352- if ( format . language [ kRelatedFormatsTitle ] ) {
353- heading . innerText = format . language [ kRelatedFormatsTitle ] ;
354- }
355- containerEl . appendChild ( heading ) ;
356-
357- const formatList = doc . createElement ( "ul" ) ;
358-
359- for ( const renderedFormat of options . renderedFormats ) {
360- if ( ! isHtmlOutput ( renderedFormat . format . pandoc , true ) ) {
361- const li = doc . createElement ( "li" ) ;
362-
363- const link = doc . createElement ( "a" ) ;
364- link . setAttribute ( "href" , renderedFormat . path ) ;
365- const dlAttrValue = dlName (
366- renderedFormat . format ,
367- renderedFormat . path ,
368- ) ;
369- if ( dlAttrValue ) {
370- link . setAttribute ( "download" , dlAttrValue ) ;
371- }
372-
373- const icon = doc . createElement ( "i" ) ;
374- icon . classList . add ( "bi" ) ;
375- icon . classList . add ( `bi-${ bsIcon ( renderedFormat . format ) } ` ) ;
376- link . appendChild ( icon ) ;
377- link . appendChild (
378- doc . createTextNode ( `${ renderedFormat . format . pandoc . to } ` ) ,
379- ) ;
321+ processAlternateFormatLinks ( options , doc , format , resources ) ;
380322
381- li . appendChild ( link ) ;
382- formatList . appendChild ( li ) ;
383- }
384- }
385- containerEl . appendChild ( formatList ) ;
386- dlLinkTarget . appendChild ( containerEl ) ;
387- }
388- }
323+ // Look for included / embedded notebooks and include those
324+ processNotebookEmbeds ( doc , format , resources ) ;
389325
390326 // default treatment for computational tables
391327 const addTableClasses = ( table : Element , computational = false ) => {
@@ -447,7 +383,6 @@ function bootstrapHtmlPostprocessor(
447383 }
448384
449385 // Process the title elements of this document
450- const resources : string [ ] = [ ] ;
451386 const titleResourceFiles = processDocumentTitle (
452387 input ,
453388 format ,
@@ -476,6 +411,152 @@ function bootstrapHtmlPostprocessor(
476411 } ;
477412}
478413
414+ // Provides a download name for a format/path
415+ const fileDownloadAttr = ( format : Format , path : string ) => {
416+ if ( isIpynbOutput ( format . pandoc ) ) {
417+ return basename ( path ) ;
418+ } else {
419+ return undefined ;
420+ }
421+ } ;
422+
423+ // Provides an icon for a format
424+ const fileBsIconName = ( format : Format ) => {
425+ if ( isDocxOutput ( format . pandoc ) ) {
426+ return "file-word" ;
427+ } else if ( isPdfOutput ( format . pandoc ) ) {
428+ return "file-pdf" ;
429+ } else if ( isIpynbOutput ( format . pandoc ) ) {
430+ return "journal-arrow-down" ;
431+ } else if ( isMarkdownOutput ( format . pandoc ) ) {
432+ return "file-code" ;
433+ } else if ( isPresentationOutput ( format . pandoc ) ) {
434+ return "file-slides" ;
435+ } else {
436+ return "file" ;
437+ }
438+ } ;
439+
440+ function processAlternateFormatLinks (
441+ options : {
442+ inputMetadata : Metadata ;
443+ inputTraits : PandocInputTraits ;
444+ renderedFormats : RenderedFormat [ ] ;
445+ } ,
446+ doc : Document ,
447+ format : Format ,
448+ resources : string [ ] ,
449+ ) {
450+ if ( options . renderedFormats . length > 1 ) {
451+ let dlLinkTarget = doc . querySelector ( `nav[role="doc-toc"]` ) ;
452+ if ( dlLinkTarget === null ) {
453+ dlLinkTarget = doc . querySelector ( "#quarto-margin-sidebar" ) ;
454+ }
455+
456+ if ( dlLinkTarget ) {
457+ const containerEl = doc . createElement ( "div" ) ;
458+ containerEl . classList . add ( "quarto-alternate-formats" ) ;
459+
460+ const heading = doc . createElement ( "h2" ) ;
461+ if ( format . language [ kRelatedFormatsTitle ] ) {
462+ heading . innerText = format . language [ kRelatedFormatsTitle ] ;
463+ }
464+ containerEl . appendChild ( heading ) ;
465+
466+ const formatList = doc . createElement ( "ul" ) ;
467+
468+ for ( const renderedFormat of options . renderedFormats ) {
469+ if ( ! isHtmlOutput ( renderedFormat . format . pandoc , true ) ) {
470+ const li = doc . createElement ( "li" ) ;
471+
472+ const link = doc . createElement ( "a" ) ;
473+ link . setAttribute ( "href" , renderedFormat . path ) ;
474+ const dlAttrValue = fileDownloadAttr (
475+ renderedFormat . format ,
476+ renderedFormat . path ,
477+ ) ;
478+ if ( dlAttrValue ) {
479+ link . setAttribute ( "download" , dlAttrValue ) ;
480+ }
481+
482+ const icon = doc . createElement ( "i" ) ;
483+ icon . classList . add ( "bi" ) ;
484+ icon . classList . add ( `bi-${ fileBsIconName ( renderedFormat . format ) } ` ) ;
485+ link . appendChild ( icon ) ;
486+ link . appendChild (
487+ doc . createTextNode ( `${ renderedFormat . format . pandoc . to } ` ) ,
488+ ) ;
489+
490+ li . appendChild ( link ) ;
491+ formatList . appendChild ( li ) ;
492+
493+ resources . push ( renderedFormat . path ) ;
494+ }
495+ }
496+ containerEl . appendChild ( formatList ) ;
497+ dlLinkTarget . appendChild ( containerEl ) ;
498+ }
499+ }
500+ }
501+
502+ function processNotebookEmbeds (
503+ doc : Document ,
504+ format : Format ,
505+ resources : string [ ] ,
506+ ) {
507+ const notebookDivNodes = doc . querySelectorAll ( "[data-notebook]" ) ;
508+ if ( notebookDivNodes . length > 0 ) {
509+ const nbPaths : string [ ] = [ ] ;
510+ notebookDivNodes . forEach ( ( nbDivNode ) => {
511+ const nbDivEl = nbDivNode as Element ;
512+ const nbPath = nbDivEl . getAttribute ( "data-notebook" ) ;
513+ if ( nbPath ) {
514+ nbPaths . push ( nbPath ) ;
515+ }
516+ } ) ;
517+
518+ const containerEl = doc . createElement ( "div" ) ;
519+ containerEl . classList . add ( "quarto-alternate-notebooks" ) ;
520+
521+ const heading = doc . createElement ( "h2" ) ;
522+ if ( format . language [ kRelatedNotebooksTitle ] ) {
523+ heading . innerText = format . language [ kRelatedNotebooksTitle ] ;
524+ }
525+ containerEl . appendChild ( heading ) ;
526+
527+ const formatList = doc . createElement ( "ul" ) ;
528+ containerEl . appendChild ( formatList ) ;
529+ ld . uniq ( nbPaths ) . forEach ( ( nbPath ) => {
530+ const li = doc . createElement ( "li" ) ;
531+
532+ const link = doc . createElement ( "a" ) ;
533+ link . setAttribute ( "href" , nbPath ) ;
534+ link . setAttribute ( "download" , basename ( nbPath ) ) ;
535+
536+ const icon = doc . createElement ( "i" ) ;
537+ icon . classList . add ( "bi" ) ;
538+ icon . classList . add ( `bi-journal-arrow-down` ) ;
539+ link . appendChild ( icon ) ;
540+ link . appendChild (
541+ doc . createTextNode ( `${ basename ( nbPath ) } ` ) ,
542+ ) ;
543+
544+ li . appendChild ( link ) ;
545+ formatList . appendChild ( li ) ;
546+
547+ resources . push ( nbPath ) ;
548+ } ) ;
549+ let dlLinkTarget = doc . querySelector ( `nav[role="doc-toc"]` ) ;
550+ if ( dlLinkTarget === null ) {
551+ dlLinkTarget = doc . querySelector ( "#quarto-margin-sidebar" ) ;
552+ }
553+
554+ if ( dlLinkTarget ) {
555+ dlLinkTarget . appendChild ( containerEl ) ;
556+ }
557+ }
558+ }
559+
479560function bootstrapHtmlFinalizer ( format : Format , flags : PandocFlags ) {
480561 return ( doc : Document ) : Promise < void > => {
481562 const { citesInMargin, refsInMargin } = processColumnElements (
0 commit comments