import { basename, isAbsolute, join } from "../../deno_ral/path.ts";
import {
kEmbedResources,
kHtmlMathMethod,
kSelfContained,
kSelfContainedMath,
} from "../../config/constants.ts";
import {
Format,
FormatExtras,
FormatTemplateContext,
Metadata,
} from "../../config/types.ts";
import { copyTo } from "../../core/copy.ts";
import { PandocOptions, RenderFlags } from "./types.ts";
import * as ld from "../../core/lodash.ts";
import { isHtmlDocOutput, isRevealjsOutput } from "../../config/format.ts";
import { expandGlobSync } from "../../deno_ral/fs.ts";
import { normalizePath } from "../../core/path.ts";
import { isGlob } from "../../core/lib/glob.ts";
import { ProjectContext } from "../../project/types.ts";
import { isWindows } from "../../deno_ral/platform.ts";
export const kPatchedTemplateExt = ".patched";
export const kTemplatePartials = "template-partials";
export function readPartials(
metadata: Metadata,
inputDir?: string,
) {
if (typeof (metadata?.[kTemplatePartials]) === "string") {
metadata[kTemplatePartials] = [metadata[kTemplatePartials]];
}
const result = (metadata?.[kTemplatePartials] || []) as string[];
inputDir = inputDir ? normalizePath(inputDir) : undefined;
const resolvePath = (path: string) => {
if (!inputDir || isAbsolute(path)) {
return path;
} else {
return join(inputDir, path);
}
};
return result.flatMap((path) => {
const result = [];
for (const walk of expandGlobSync(resolvePath(path))) {
result.push(walk.path);
}
if (!isGlob(path) && result.length === 0) {
throw new Error(
`Template partial ${path} was not found. Please confirm that the path to the file is correct.`,
);
}
return result;
});
}
export function resolveTemplatePartialPaths(
metadata: Metadata,
inputDir?: string,
project?: ProjectContext,
) {
if (typeof (metadata?.[kTemplatePartials]) === "string") {
metadata[kTemplatePartials] = [metadata[kTemplatePartials]];
}
const result = (metadata?.[kTemplatePartials] || []) as string[];
metadata[kTemplatePartials] = result.map((path) => {
if (project && (path.startsWith("/") || path.startsWith("\\"))) {
return join(project.dir, path.slice(1));
} else if (!inputDir || isAbsolute(path)) {
return path;
} else {
if (isAbsolute(inputDir)) {
return join(inputDir, path);
} else {
return join(Deno.cwd(), inputDir, path);
}
}
});
}
export async function stageTemplate(
options: PandocOptions,
extras: FormatExtras,
userContext?: FormatTemplateContext,
) {
const stagingDir = options.services.temp.createDir();
const template = "template.patched";
const stageContext = (
dir: string,
template: string,
context?: FormatTemplateContext,
) => {
if (context) {
if (context.template) {
const targetFile = join(dir, template);
copyTo(context.template, targetFile);
if (!isWindows) {
Deno.chmodSync(targetFile, 0o666);
}
}
if (context.partials) {
for (const partial of context.partials) {
copyTo(partial, join(stagingDir, basename(partial)));
}
}
return true;
} else {
return false;
}
};
const formatStaged = stageContext(
stagingDir,
template,
extras.templateContext,
);
const userStaged = await stageContext(stagingDir, template, userContext);
if (formatStaged || userStaged) {
const stagedTemplatePath = join(stagingDir, template);
applyTemplatePatches(stagedTemplatePath, options.format, options.flags);
return stagedTemplatePath;
} else {
return undefined;
}
}
export function cleanTemplatePartialMetadata(
metadata: Metadata,
builtIns: string[],
) {
const partials = metadata[kTemplatePartials] as string[] | undefined;
if (partials) {
const cleansed = partials.filter((part) => builtIns.includes(part));
if (cleansed.length === 0) {
delete metadata[kTemplatePartials];
} else {
metadata[kTemplatePartials] = cleansed;
}
}
}
interface TemplatePatch {
searchValue: RegExp;
contents: string;
}
function applyTemplatePatches(
template: string,
format: Format,
flags?: RenderFlags,
) {
const patches: TemplatePatch[] = [];
if (isHtmlDocOutput(format.pandoc) || isRevealjsOutput(format.pandoc)) {
if (
((flags && flags[kSelfContained]) || format.pandoc[kSelfContained] ||
(flags && flags[kEmbedResources]) || format.pandoc[kEmbedResources]) &&
!format.render[kSelfContainedMath]
) {
const math = mathConfig(format, flags);
if (math) {
const mathTemplate = math.method === "mathjax"
? mathjaxScript(math.url)
: math.method == "katex"
? katexScript(math.url)
: "";
if (mathTemplate) {
patches.push({
searchValue: /\$math\$/,
contents: mathTemplate,
});
}
}
}
}
if (patches.length) {
let templateContents = Deno.readTextFileSync(template);
patches.forEach((patch) => {
templateContents = templateContents.replace(
patch.searchValue,
patch.contents,
);
});
Deno.writeTextFileSync(template, templateContents);
}
}
function mathConfig(format: Format, flags?: RenderFlags) {
if (
flags?.mathjax || flags?.katex || flags?.webtex || flags?.gladtex ||
flags?.mathml
) {
return undefined;
}
const math = format.pandoc[kHtmlMathMethod];
if (math === undefined || math === "mathjax") {
return {
method: "mathjax",
url: "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js",
};
} else if (math === "katex") {
return {
method: "katex",
url: "https://cdn.jsdelivr.net/npm/[email protected]/dist/",
};
} else if (ld.isObject(math)) {
const mathMethod = math as { method: string; url: string };
if (
(mathMethod.method === "mathjax" || mathMethod.method === "katex") &&
typeof (mathMethod.url) === "string"
) {
return mathMethod;
}
}
}
function mathjaxScript(url: string) {
return `
<script>
(function () {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "${url}";
document.getElementsByTagName("head")[0].appendChild(script);
})();
</script>
`;
}
function katexScript(url: string) {
url = url.trim();
if (!url.endsWith("/")) {
url += "/";
}
return `
<script>
document.addEventListener("DOMContentLoaded", function () {
var head = document.getElementsByTagName("head")[0];
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "${url}katex.min.css";
head.appendChild(link);
function renderMathElements() {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
if (window.katex) {
window.katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
} else {
console.error("KaTeX has not been loaded correctly, as not found globally.");
}
}
}
}
var script = document.createElement("script");
script.src = "${url}katex.min.js";
script.onload = renderMathElements;
// Check for RequireJS and AMD detection as it conflicts with KaTeX loading.
if (typeof require === 'function' && typeof define === 'function' && define.amd) {
// Disable require.js AMD detection temporarily, as it conflicts with KaTeX loading using CommonJS
var disableAmdScript = document.createElement("script");
disableAmdScript.textContent = 'window._amd_backup = window.define.amd; window.define.amd = false;';
head.appendChild(disableAmdScript);
// overwrite onload to restore Require.js AMD detection
script.onload = function() {
// Restore Require.js AMD detection
window.define.amd = window._amd_backup;
delete window._amd_backup;
renderMathElements();
};
}
head.appendChild(script);
});
</script>
`;
}