Path: blob/main/src/format/html/format-html-links.ts
6450 views
/*1* format-html-links.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/56import {7basename,8dirname,9extname,10isAbsolute,11relative,12} from "../../deno_ral/path.ts";1314import {15kDisplayName,16kExtensionName,17kFormatLinks,18kTargetFormat,19} from "../../config/constants.ts";20import { Format, FormatAliasLink, FormatLink } from "../../config/types.ts";2122import { RenderedFormat } from "../../command/render/types.ts";23import {24isDocxOutput,25isHtmlOutput,26isIpynbOutput,27isJatsOutput,28isMarkdownOutput,29isPdfOutput,30isPresentationOutput,31isTypstOutput,32} from "../../config/format.ts";3334export interface AlternateLink {35title: string;36href: string;37icon: string;38order: number;39dlAttrValue?: string;40attr?: Record<string, string>;41}4243export function otherFormatLinks(44input: string,45format: Format,46renderedFormats: RenderedFormat[],47) {48const normalizedFormatLinks = (49unnormalizedLinks: unknown,50): Array<string | FormatLink | FormatAliasLink> | undefined => {51if (typeof unnormalizedLinks === "boolean") {52return undefined;53} else if (unnormalizedLinks !== undefined) {54const linksArr: unknown[] = Array.isArray(unnormalizedLinks)55? unnormalizedLinks56: [unnormalizedLinks];57return linksArr as Array<string | FormatLink | FormatAliasLink>;58} else {59return undefined;60}61};62const userLinks = normalizedFormatLinks(format.render[kFormatLinks]);6364// Don't include HTML output65const filteredFormats = renderedFormats.filter(66(renderedFormat) => {67return !isHtmlOutput(renderedFormat.format.pandoc, true);68},69);7071return alternateLinks(72input,73filteredFormats,74userLinks,75);76}7778export function alternateLinks(79input: string,80formats: RenderedFormat[],81userLinks?: Array<string | FormatLink | FormatAliasLink>,82): AlternateLink[] {83const alternateLinks: AlternateLink[] = [];8485const alternateLinkForFormat = (86renderedFormat: RenderedFormat,87order: number,88title?: string,89icon?: string,90) => {91const relPath = isAbsolute(renderedFormat.path)92? relative(dirname(input), renderedFormat.path)93: renderedFormat.path;94return {95title: `${96title ||97renderedFormat.format.identifier[kDisplayName] ||98renderedFormat.format.pandoc.to99}${100renderedFormat.format.identifier[kExtensionName]101? ` (${renderedFormat.format.identifier[kExtensionName]})`102: ""103}`,104href: relPath,105icon: icon || fileBsIconName(renderedFormat.format),106order,107dlAttrValue: fileDownloadAttr(108renderedFormat.format,109renderedFormat.path,110),111};112};113114let count = 1;115for (const userLink of userLinks || []) {116if (typeof userLink === "string") {117// We need to filter formats, otherwise, we'll deal118// with them below119const renderedFormat = formats.find((f) =>120f.format.identifier[kTargetFormat] === userLink121);122if (renderedFormat) {123// Just push through124alternateLinks.push(alternateLinkForFormat(renderedFormat, count));125}126} else {127const linkObj = userLink as FormatLink | FormatAliasLink;128if ("format" in linkObj) {129const thatLink = userLink as FormatAliasLink;130const rf = formats.find((f) =>131f.format.identifier[kTargetFormat] === thatLink.format132);133if (rf) {134// Just push through135alternateLinks.push(136alternateLinkForFormat(rf, count, thatLink.text, thatLink.icon),137);138}139} else {140// This an explicit link141const thisLink = userLink as FormatLink;142const alternate = {143title: thisLink.text,144href: thisLink.href,145icon: thisLink.icon || fileBsIconForExt(thisLink.href),146dlAttrValue: "",147order: thisLink.order || count,148attr: thisLink.attr,149};150alternateLinks.push(alternate);151}152}153count++;154}155156const userLinksHasFormat = userLinks &&157userLinks.some((link) => typeof link === "string");158if (!userLinksHasFormat) {159formats.forEach((renderedFormat) => {160const baseFormat = renderedFormat.format.identifier["base-format"];161if (!kExcludeFormatUnlessExplicit.includes(baseFormat || "html")) {162alternateLinks.push(alternateLinkForFormat(renderedFormat, count));163}164count++;165});166}167168return alternateLinks;169}170171// Provides an icon for a format172const fileBsIconName = (format: Format) => {173if (isDocxOutput(format.pandoc)) {174return "file-word";175} else if (isPdfOutput(format.pandoc)) {176return "file-pdf";177} else if (isTypstOutput(format.pandoc)) {178return "file-pdf";179} else if (isIpynbOutput(format.pandoc)) {180return "journal-code";181} else if (isMarkdownOutput(format)) {182return "file-code";183} else if (isPresentationOutput(format.pandoc)) {184return "file-slides";185} else if (isJatsOutput(format.pandoc)) {186return "filetype-xml";187} else {188return "file";189}190};191192// Provides a download name for a format/path193const fileDownloadAttr = (format: Format, path: string) => {194if (isIpynbOutput(format.pandoc)) {195return basename(path);196} else if (isJatsOutput(format.pandoc)) {197return basename(path);198} else {199return undefined;200}201};202203const fileBsIconForExt = (path: string) => {204const ext = extname(path);205switch (ext.toLowerCase()) {206case ".docx":207return "file-word";208case ".pdf":209return "file-pdf";210case ".ipynb":211return "journal-code";212case ".md":213return "file-code";214case ".xml":215return "filetype-xml";216default:217return "file";218}219};220221const kExcludeFormatUnlessExplicit = ["jats"];222223224