Path: blob/main/src/format/html/format-html-shared.ts
6450 views
/*1* format-html-shared.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/5import { dirname, join, relative } from "../../deno_ral/path.ts";6import { outputVariable, sassLayer, sassVariable } from "../../core/sass.ts";7import {8kCapLoc,9kCapTop,10kCitationLocation,11kCodeOverflow,12kCopyButtonTooltip,13kFigCapLoc,14kLinkExternalIcon,15kReferenceLocation,16kSectionTitleFootnotes,17kSectionTitleReferences,18kTblCapLoc,19} from "../../config/constants.ts";20import {21Format,22FormatDependency,23FormatLanguage,24PandocFlags,25} from "../../config/types.ts";2627import { formatResourcePath } from "../../core/resources.ts";28import { Document, Element } from "../../core/deno-dom.ts";29import { normalizePath } from "../../core/path.ts";3031// features that are enabled by default for 'html'. setting32// all of these to false will yield the minimal html output33// that quarto can produce (there is still some CSS we generate34// to provide figure layout, etc.). you can also set the35// 'minimal' option to do this in one shot36export const kTabsets = "tabsets";37export const kCodeCopy = "code-copy";38export const kAnchorSections = "anchor-sections";39export const kCitationsHover = "citations-hover";40export const kFootnotesHover = "footnotes-hover";41export const kXrefsHover = "crossrefs-hover";42export const kSmoothScroll = "smooth-scroll";4344// Code Annotation45export const kCodeAnnotations = "code-annotations";4647// turn off optional html features as well as all themes48export const kMinimal = "minimal";4950export const kPageLayout = "page-layout";51export const kPageLayoutArticle = "article";52export const kPageLayoutCustom = "custom";53export const kPageLayoutFull = "full";54export const kComments = "comments";55export const kHypothesis = "hypothesis";56export const kUtterances = "utterances";57export const kGiscus = "giscus";58export const kAxe = "axe";5960export const kGiscusRepoId = "repo-id";61export const kGiscusCategoryId = "category-id";6263export const kDraft = "draft";6465export const kAppendixStyle = "appendix-style";66export const kAppendixCiteAs = "appendix-cite-as";67export const kLicense = "license";68export const kCopyright = "copyright";6970export const kCitation = "citation";7172export const kDocumentCss = "document-css";73export const kBootstrapDependencyName = "bootstrap";7475export const clipboardDependency = () => {76const dependency: FormatDependency = { name: "clipboard" };77dependency.scripts = [];78dependency.scripts.push({79name: "clipboard.min.js",80path: formatResourcePath("html", join("clipboard", "clipboard.min.js")),81});82return dependency;83};8485export const bootstrapFunctions = () => {86return Deno.readTextFileSync(87join(bootstrapResourceDir(), "_functions.scss"),88);89};9091export const bootstrapMixins = () => {92return Deno.readTextFileSync(93join(bootstrapResourceDir(), "_mixins.scss"),94);95};9697export const bootstrapVariables = () => {98return Deno.readTextFileSync(99join(bootstrapResourceDir(), "_variables.scss"),100);101};102103export const bootstrapRules = () => {104return Deno.readTextFileSync(105join(bootstrapResourceDir(), "bootstrap.scss"),106);107};108109export const bslibComponentMixins = () => {110const bootstrapDistDir = formatResourcePath(111"html",112"bslib",113);114const mixinsDir = join(bootstrapDistDir, "components", "scss", "mixins");115return Deno.readTextFileSync(join(mixinsDir, "_mixins.scss"));116};117118export const bslibComponentRules = () => {119const bslibDistDir = formatResourcePath(120"html",121join("bslib"),122);123124const bslibDirs = [125join(bslibDistDir, "bslib-scss"),126join(bslibDistDir, "components", "scss"),127];128129const scss = [];130for (const bsLibDir of bslibDirs) {131for (132const walk of Deno.readDirSync(bsLibDir)133) {134if (walk.isFile) {135const contents = Deno.readTextFileSync(join(bsLibDir, walk.name));136scss.push(contents);137}138}139}140141return scss.join("\n");142};143144export const htmlToolsRules = () => {145const htmlToolsDir = formatResourcePath(146"html",147join("htmltools"),148);149const fillCss = Deno.readTextFileSync(join(htmlToolsDir, "fill.css"));150return fillCss;151};152153export const bootstrapResourceDir = () => {154return formatResourcePath(155"html",156join("bootstrap", "dist", "scss"),157);158};159160export const bslibResourceDir = () => {161return formatResourcePath(162"html",163join("bslib", "bslib-scss"),164);165};166167export const sassUtilFunctions = (name: string) => {168const bootstrapDistDir = formatResourcePath(169"html",170join("bootstrap", "dist"),171);172173const path = join(bootstrapDistDir, "sass-utils", name);174return Deno.readTextFileSync(path);175};176177export const quartoRules = () =>178Deno.readTextFileSync(formatResourcePath(179"html",180"_quarto-rules.scss",181));182183export const quartoCopyCodeDefaults = () =>184Deno.readTextFileSync(formatResourcePath(185"html",186"_quarto-variables-copy-code.scss",187));188189export const quartoCopyCodeRules = () =>190Deno.readTextFileSync(formatResourcePath(191"html",192"_quarto-rules-copy-code.scss",193));194195export const quartoLinkExternalRules = () =>196Deno.readTextFileSync(formatResourcePath(197"html",198"_quarto-rules-link-external.scss",199));200201export const quartoCodeFilenameRules = () =>202Deno.readTextFileSync(formatResourcePath(203"html",204"_quarto-rules-code-filename.scss",205));206207export const quartoTabbyRules = () =>208Deno.readTextFileSync(formatResourcePath(209"html",210"_quarto-rules-tabby.scss",211));212213export const quartoFigResponsiveRules = () => {214return [215".img-fluid {",216" max-width: 100%;",217" height: auto;",218"}",219].join("\n");220};221222export const quartoGlobalCssVariableRules = () => {223return `224$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;225/*! quarto-variables-start */226:root {227--quarto-font-monospace: #{inspect($font-family-monospace)};228}229/*! quarto-variables-end */230`;231};232export const quartoBootstrapCustomizationLayer = () => {233const path = formatResourcePath(234"html",235join("bootstrap", "_bootstrap-customize.scss"),236);237return sassLayer(path);238};239240export const quartoBootstrapRules = () =>241Deno.readTextFileSync(formatResourcePath(242"html",243join("bootstrap", "_bootstrap-rules.scss"),244));245246export const quartoBootstrapMixins = () =>247Deno.readTextFileSync(formatResourcePath(248"html",249join("bootstrap", "_bootstrap-mixins.scss"),250));251252export const quartoBootstrapFunctions = () =>253Deno.readTextFileSync(formatResourcePath(254"html",255join("bootstrap", "_bootstrap-functions.scss"),256));257258export const quartoBaseLayer = (259format: Format,260codeCopy = false,261tabby = false,262figResponsive = false,263codeFilename = false,264) => {265const rules: string[] = [quartoRules()];266const defaults: string[] = [quartoDefaults(format), quartoVariables()];267if (codeCopy) {268rules.push(quartoCopyCodeRules());269defaults.push(quartoCopyCodeDefaults());270}271if (tabby) {272rules.push(quartoTabbyRules());273}274if (figResponsive) {275rules.push(quartoFigResponsiveRules());276}277if (codeFilename) {278rules.push(quartoCodeFilenameRules());279}280if (format.render[kLinkExternalIcon]) {281rules.push(quartoLinkExternalRules());282}283284return {285uses: quartoUses(),286defaults: defaults.join("\n"),287functions: quartoFunctions(),288mixins: "",289rules: rules.join("\n"),290};291};292293export const quartoVariables = () =>294Deno.readTextFileSync(formatResourcePath(295"html",296"_quarto-variables.scss",297));298299export const quartoUses = () =>300Deno.readTextFileSync(formatResourcePath(301"html",302"_quarto-uses.scss",303));304305export const quartoFunctions = () =>306Deno.readTextFileSync(formatResourcePath(307"html",308"_quarto-functions.scss",309));310311export const quartoDefaults = (format: Format) => {312const defaults: string[] = [];313defaults.push(314outputVariable(315sassVariable(316"code-copy-selector",317format.metadata[kCodeCopy] === undefined ||318format.metadata[kCodeCopy] === "hover"319// ? '"div.sourceCode:hover > "'320? '"div.code-copy-outer-scaffold:hover > "'321: '""',322),323),324);325defaults.push(326outputVariable(327sassVariable(328"code-white-space",329format.render[kCodeOverflow] === "wrap" ? "pre-wrap" : "pre",330),331),332);333defaults.push(334outputVariable(335sassVariable(336kTblCapLoc,337format.metadata[kTblCapLoc] ||338format.metadata[kCapLoc] || kCapTop,339),340),341);342return defaults.join("\n");343};344345export function insertFootnotesTitle(346doc: Document,347footnotesEl: Element,348language: FormatLanguage,349level = 2,350classes: string[] = [],351) {352prependHeading(353doc,354footnotesEl,355language[kSectionTitleFootnotes],356level,357classes,358);359}360361export function insertReferencesTitle(362doc: Document,363refsEl: Element,364language: FormatLanguage,365level = 2,366classes: string[] = [],367) {368prependHeading(369doc,370refsEl,371language[kSectionTitleReferences],372level,373classes,374);375}376377export function insertTitle(378doc: Document,379el: Element,380title: string,381level = 2,382headingClasses: string[] = [],383) {384prependHeading(doc, el, title, level, headingClasses);385}386387function prependHeading(388doc: Document,389el: Element,390title: string | undefined,391level: number,392classes: string[],393) {394const heading = doc.createElement("h" + level);395if (typeof title == "string" && title !== "none") {396heading.innerHTML = title;397}398if (classes) {399classes.forEach((clz) => {400heading.classList.add(clz);401});402}403404el.insertBefore(heading, el.firstChild);405const hr = el.querySelector("hr");406if (hr) {407hr.remove();408}409}410411export function removeFootnoteBacklinks(footnotesEl: Element) {412const backlinks = footnotesEl.querySelectorAll(".footnote-back");413for (const backlink of backlinks) {414(backlink as Element).remove();415}416}417418export function setMainColumn(doc: Document, column: string) {419const selectors = [420"main.content",421".page-navigation",422".quarto-title-banner .quarto-title",423".quarto-title-block .quarto-title-meta-author",424".quarto-title-block .quarto-title-meta",425"div[class^='quarto-about-']",426"div[class*=' quarto-about-']",427];428selectors.forEach((selector) => {429const el = doc.querySelector(selector);430if (el) {431// Clear existing column432for (const clz of el.classList) {433if (clz.startsWith("column-")) {434el.classList.remove(clz);435}436}437438// Set the new column439el.classList.add(column);440}441});442}443444export function hasMarginRefs(format: Format, flags: PandocFlags) {445// If margin footnotes are enabled move them446return format.pandoc[kReferenceLocation] === "margin" ||447flags[kReferenceLocation] === "margin";448}449450export function hasMarginCites(format: Format) {451// If margin cites are enabled, move them452return format.metadata[kCitationLocation] === "margin";453}454455export function hasMarginFigCaps(format: Format) {456return format.metadata[kFigCapLoc] === "margin";457}458459export function computeUrl(460input: string,461baseUrl: string,462offset: string,463outputFileName: string,464) {465const rootDir = normalizePath(join(dirname(input), offset));466if (outputFileName === "index.html") {467return `${baseUrl}/${relative(rootDir, dirname(input))}`;468} else {469return `${baseUrl}/${470relative(rootDir, join(dirname(input), outputFileName))471}`;472}473}474475export function createCodeCopyButton(doc: Document, format: Format) {476const copyButton = doc.createElement("button");477const title = format.language[kCopyButtonTooltip]!;478copyButton.setAttribute("title", title);479copyButton.classList480.add("code-copy-button");481const copyIcon = doc.createElement("i");482copyIcon.classList.add("bi");483copyButton.appendChild(copyIcon);484return copyButton;485}486487export function createCodeBlock(488doc: Document,489htmlContents: string,490language?: string,491) {492const preEl = doc.createElement("PRE");493preEl.classList.add("sourceCode");494preEl.classList.add("code-with-copy");495496const codeEl = doc.createElement("CODE");497codeEl.classList.add("sourceCode");498if (language) {499codeEl.classList.add(language);500}501codeEl.innerHTML = htmlContents;502preEl.appendChild(codeEl);503return preEl;504}505506export function writeMetaTag(name: string, content: string, doc: Document) {507// Meta tag508const m = doc.createElement("META");509if (name.startsWith("og:")) {510m.setAttribute("property", name);511} else {512m.setAttribute("name", name);513}514m.setAttribute("content", content);515516// New Line517const nl = doc.createTextNode("\n");518519// Insert the nodes520doc.querySelector("head")?.appendChild(m);521doc.querySelector("head")?.appendChild(nl);522}523524export function writeLinkTag(rel: string, href: string, doc: Document) {525// Meta tag526const l = doc.createElement("LINK");527l.setAttribute("rel", rel);528l.setAttribute("href", href);529530// New Line531const nl = doc.createTextNode("\n");532533// Insert the nodes534doc.querySelector("head")?.appendChild(l);535doc.querySelector("head")?.appendChild(nl);536}537538export function formatPageLayout(format: Format) {539return format.metadata[kPageLayout] as string || kPageLayoutArticle;540}541542export function formatHasFullLayout(format: Format) {543return format.metadata[kPageLayout] === kPageLayoutFull;544}545546export function formatHasArticleLayout(format: Format) {547return format.metadata[kPageLayout] === undefined ||548format.metadata[kPageLayout] === kPageLayoutArticle ||549format.metadata[kPageLayout] === kPageLayoutFull;550}551552553