@@ -79,6 +79,7 @@ import { isJupyterNotebook } from "../../core/jupyter/jupyter.ts";
7979import { watchForFileChanges } from "../../core/watch.ts" ;
8080import {
8181 pandocBinaryPath ,
82+ resourcePath ,
8283 textHighlightThemePath ,
8384} from "../../core/resources.ts" ;
8485import { execProcess } from "../../core/process.ts" ;
@@ -89,6 +90,8 @@ import {
8990 inputExtensionDirs ,
9091} from "../../extension/extension.ts" ;
9192import { kOutputFile } from "../../config/constants.ts" ;
93+ import { isJatsOutput } from "../../config/format.ts" ;
94+ import { kDefaultProjectFileContents } from "../../project/types/project-default.ts" ;
9295
9396interface PreviewOptions {
9497 port ?: number ;
@@ -642,17 +645,46 @@ function htmlFileRequestHandlerOptions(
642645 }
643646 } ,
644647 onFile : async ( file : string , req : Request ) => {
645- if ( isHtmlContent ( file ) ) {
648+ const staticResponse = await staticResource ( format , baseDir , file ) ;
649+ if ( staticResponse ) {
650+ const resolveBody = ( ) => {
651+ if ( staticResponse . injectClient ) {
652+ const client = reloader . clientHtml (
653+ req ,
654+ inputFile ,
655+ ) ;
656+ const contents = new TextDecoder ( ) . decode (
657+ staticResponse . contents ,
658+ ) ;
659+ return staticResponse . injectClient ( contents , client ) ;
660+ } else {
661+ return staticResponse . contents ;
662+ }
663+ } ;
664+ const body = resolveBody ( ) ;
665+
666+ return {
667+ body,
668+ contentType : staticResponse . contentType ,
669+ } ;
670+ } else if ( isHtmlContent ( file ) ) {
646671 // does the provide an alternate preview file?
647672 if ( format . formatPreviewFile ) {
648673 file = format . formatPreviewFile ( file , format ) ;
649674 }
650675 const fileContents = await Deno . readFile ( file ) ;
651676 return reloader . injectClient ( req , fileContents , inputFile ) ;
652677 } else if ( isTextContent ( file ) ) {
653- const html = await textPreviewHtml ( file , req ) ;
654- const fileContents = new TextEncoder ( ) . encode ( html ) ;
655- return reloader . injectClient ( req , fileContents , inputFile ) ;
678+ if ( isJatsOutput ( format . pandoc ) ) {
679+ const xml = await jatsPreviewXml ( file , req ) ;
680+ return {
681+ body : new TextEncoder ( ) . encode ( xml ) ,
682+ } ;
683+ } else {
684+ const html = await textPreviewHtml ( file , req ) ;
685+ const fileContents = new TextEncoder ( ) . encode ( html ) ;
686+ return reloader . injectClient ( req , fileContents , inputFile ) ;
687+ }
656688 }
657689 } ,
658690 } ;
@@ -693,8 +725,8 @@ function pdfFileRequestHandler(
693725 const url = isRStudioWorkbench ( )
694726 ? await rswURL ( port , kPdfJsInitialPath )
695727 : isVSCodeServer ( )
696- ? vsCodeServerProxyUri ( ) ! . replace ( "{{port}}" , `${ port } ` )
697- + kPdfJsInitialPath
728+ ? vsCodeServerProxyUri ( ) ! . replace ( "{{port}}" , `${ port } ` ) +
729+ kPdfJsInitialPath
698730 : "/" + kPdfJsInitialPath ;
699731 return Promise . resolve ( serveRedirect ( url ) ) ;
700732 } else {
@@ -777,3 +809,75 @@ async function textPreviewHtml(file: string, req: Request) {
777809 throw new Error ( ) ;
778810 }
779811}
812+
813+ // Static reources provide a list of 'special' resources that we should
814+ // satisfy using internal resources
815+ const kStaticResources = [
816+ {
817+ name : "quarto-jats-preview.css" ,
818+ contentType : "text/css" ,
819+ isActive : isJatsOutput ,
820+ } ,
821+ {
822+ name : "quarto-jats-html.xsl" ,
823+ contentType : "text/xsl" ,
824+ isActive : isJatsOutput ,
825+ injectClient : ( contents : string , client : string ) => {
826+ const protectedClient = client . replaceAll (
827+ / ( < s t y l e .* ?> ) | ( < s c r i p t .* ?> ) / g,
828+ ( substring : string ) => {
829+ return `${ substring } \n<![CDATA[` ;
830+ } ,
831+ ) . replaceAll (
832+ / ( < \/ s t y l e .* ?> ) | ( < \/ s c r i p t .* ?> ) / g,
833+ ( substring : string ) => {
834+ return `]]>\n${ substring } ` ;
835+ } ,
836+ ) . replaceAll ( "data-micromodal-close" , 'data-micromodal-close="true"' ) ;
837+
838+ const bodyContents = contents . replace (
839+ "<!-- quarto-after-body -->" ,
840+ protectedClient ,
841+ ) ;
842+ return new TextEncoder ( ) . encode ( bodyContents ) ;
843+ } ,
844+ } ,
845+ ] ;
846+
847+ const staticResource = async (
848+ format : Format ,
849+ baseDir : string ,
850+ file : string ,
851+ ) => {
852+ const filename = relative ( baseDir , file ) ;
853+ const resource = kStaticResources . find ( ( resource ) => {
854+ return resource . isActive ( format . pandoc ) && resource . name === filename ;
855+ } ) ;
856+
857+ if ( resource ) {
858+ const path = resourcePath ( join ( "preview" , "jats" , filename ) ) ;
859+ const contents = await Deno . readFile ( path ) ;
860+ return {
861+ ...resource ,
862+ contents,
863+ } ;
864+ }
865+ } ;
866+
867+ async function jatsPreviewXml ( file : string , _request : Request ) {
868+ const fileContents = await Deno . readTextFile ( file ) ;
869+
870+ // Attach the stylesheet
871+ let xmlContents = fileContents . replace (
872+ '<?xml version="1.0" encoding="utf-8" ?>' ,
873+ '<?xml version="1.0" encoding="utf-8" ?>\n<?xml-stylesheet href="quarto-jats-html.xsl" type="text/xsl" ?>' ,
874+ ) ;
875+
876+ // Strip the DTD to disable the fetching of the DTD and validation (for preview)
877+ xmlContents = xmlContents . replace (
878+ / < ! D O C T Y P E ( ( .| \n ) * ?) > / ,
879+ "" ,
880+ ) ;
881+
882+ return xmlContents ;
883+ }
0 commit comments