Skip to content

Commit 8ac1640

Browse files
committed
Add Support for JATS preview
1 parent 9e07ede commit 8ac1640

File tree

6 files changed

+4390
-13
lines changed

6 files changed

+4390
-13
lines changed

src/command/preview/preview.ts

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import { isJupyterNotebook } from "../../core/jupyter/jupyter.ts";
7979
import { watchForFileChanges } from "../../core/watch.ts";
8080
import {
8181
pandocBinaryPath,
82+
resourcePath,
8283
textHighlightThemePath,
8384
} from "../../core/resources.ts";
8485
import { execProcess } from "../../core/process.ts";
@@ -89,6 +90,8 @@ import {
8990
inputExtensionDirs,
9091
} from "../../extension/extension.ts";
9192
import { kOutputFile } from "../../config/constants.ts";
93+
import { isJatsOutput } from "../../config/format.ts";
94+
import { kDefaultProjectFileContents } from "../../project/types/project-default.ts";
9295

9396
interface 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+
/(<style.*?>)|(<script.*?>)/g,
828+
(substring: string) => {
829+
return `${substring}\n<![CDATA[`;
830+
},
831+
).replaceAll(
832+
/(<\/style.*?>)|(<\/script.*?>)/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+
/<!DOCTYPE((.|\n)*?)>/,
879+
"",
880+
);
881+
882+
return xmlContents;
883+
}

src/config/format.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ export function isHtmlSlideOutput(format?: string | FormatPandoc) {
9797
].includes(format);
9898
}
9999

100+
export function isJatsOutput(format?: string | FormatPandoc) {
101+
if (typeof (format) !== "string") {
102+
format = format?.to || "html";
103+
}
104+
return [
105+
"jats",
106+
"jats_archiving",
107+
"jats_articleauthoring",
108+
"jats_publishing"
109+
].includes(format);
110+
}
111+
100112
export function isPresentationOutput(format: FormatPandoc) {
101113
if (format.to) {
102114
return ["s5", "dzslides", "slidy", "slideous", "revealjs", "beamer", "pptx"]

src/core/http-devserver.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export interface HttpDevServer {
2525
file: Uint8Array,
2626
inputFile?: string,
2727
) => FileResponse;
28+
clientHtml: (
29+
req: Request,
30+
inputFile?: string,
31+
) => string;
2832
reloadClients: (reloadTarget?: string) => Promise<void>;
2933
hasClients: () => boolean;
3034
}
@@ -78,6 +82,13 @@ export function httpDevServer(
7882

7983
let injectClientInitialized = false;
8084
let iframeURL: URL | undefined;
85+
const getiFrameURL = (req: Request) => {
86+
if (!injectClientInitialized) {
87+
iframeURL = viewerIFrameURL(req);
88+
injectClientInitialized = true;
89+
}
90+
return iframeURL;
91+
};
8192

8293
return {
8394
handle: (req: Request) => {
@@ -97,23 +108,29 @@ export function httpDevServer(
97108
return Promise.resolve(undefined);
98109
}
99110
},
111+
clientHtml: (
112+
req: Request,
113+
inputFile?: string,
114+
): string => {
115+
const script = devServerClientScript(
116+
port,
117+
inputFile,
118+
isPresentation,
119+
getiFrameURL(req),
120+
);
121+
return script;
122+
},
100123
injectClient: (
101124
req: Request,
102125
file: Uint8Array,
103126
inputFile?: string,
104127
): FileResponse => {
105-
if (!injectClientInitialized) {
106-
iframeURL = viewerIFrameURL(req);
107-
injectClientInitialized = true;
108-
}
109-
110128
const script = devServerClientScript(
111129
port,
112130
inputFile,
113131
isPresentation,
114-
iframeURL,
132+
getiFrameURL(req),
115133
);
116-
117134
const scriptContents = new TextEncoder().encode("\n" + script);
118135
const fileWithScript = new Uint8Array(
119136
file.length + scriptContents.length,

src/project/serve/watch.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,9 @@ export function watchProject(
354354
injectClient: (req: Request, file: Uint8Array, inputFile?: string) => {
355355
return devServer.injectClient(req, file, inputFile);
356356
},
357+
clientHtml: (req: Request, inputFile?: string) => {
358+
return devServer.clientHtml(req, inputFile);
359+
},
357360
hasClients: () => devServer.hasClients(),
358361
reloadClients: async (output: boolean, reloadTarget?: string) => {
359362
await reloadClients({

0 commit comments

Comments
 (0)