-
Notifications
You must be signed in to change notification settings - Fork 331
/
format-html-title.ts
318 lines (285 loc) · 9.2 KB
/
format-html-title.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
* format-html-title.ts
*
* Copyright (C) 2020-2022 Posit Software, PBC
*/
import { existsSync } from "../../deno_ral/fs.ts";
import { dirname, isAbsolute, join } from "../../deno_ral/path.ts";
import { kDateFormat, kTocLocation } from "../../config/constants.ts";
import { Format, Metadata, PandocFlags } from "../../config/types.ts";
import { Document, Element } from "../../core/deno-dom.ts";
import { formatResourcePath } from "../../core/resources.ts";
import { sassLayer } from "../../core/sass.ts";
import { TempContext } from "../../core/temp-types.ts";
import { MarkdownPipeline } from "../../core/markdown-pipeline.ts";
import {
HtmlPostProcessResult,
PandocInputTraits,
RenderedFormat,
} from "../../command/render/types.ts";
import { InternalError } from "../../core/lib/error.ts";
export const kTitleBlockStyle = "title-block-style";
const kTitleBlockBanner = "title-block-banner";
const ktitleBlockColor = "title-block-banner-color";
const kTitleBlockCategories = "title-block-categories";
export interface DocumentTitleContext {
pipeline: MarkdownPipeline;
}
export function documentTitleScssLayer(format: Format) {
if (
format.metadata[kTitleBlockStyle] === false ||
format.metadata[kTitleBlockStyle] === "none" ||
format.metadata[kTitleBlockStyle] === "plain"
) {
return undefined;
} else if (format.metadata[kTitleBlockStyle] === "manuscript") {
// TODO: Tweak style for manuscript
// This code path is here so that we can add manuscript-specific styles
// For now it is just identical to non-manuscript
const titleBlockScss = formatResourcePath(
"html",
join("templates", "title-block.scss"),
);
return sassLayer(titleBlockScss);
} else {
const titleBlockScss = formatResourcePath(
"html",
join("templates", "title-block.scss"),
);
return sassLayer(titleBlockScss);
}
}
export function documentTitleMetadata(
format: Format,
) {
if (
format.metadata[kTitleBlockStyle] !== false &&
format.metadata[kTitleBlockStyle] !== "none" &&
format.metadata[kDateFormat] === undefined
) {
return {
[kDateFormat]: "long",
};
} else {
return undefined;
}
}
export function documentTitleIncludeInHeader(
input: string,
format: Format,
temp: TempContext,
) {
// Inject variables
const headingVars: string[] = [];
const containerVars: string[] = [];
const banner = format.metadata[kTitleBlockBanner] as string | boolean;
if (banner) {
// $title-banner-bg
// $title-banner-color
// $title-banner-image
const titleBlockColor = titleColor(format.metadata[ktitleBlockColor]);
if (titleBlockColor) {
const color = `color: ${titleBlockColor};`;
headingVars.push(color);
containerVars.push(color);
}
if (banner === true) {
// The default appearance, use navbar color
} else if (isBannerImage(input, banner)) {
// An image background
containerVars.push(`background-image: url(${banner});`);
containerVars.push(`background-size: cover;`);
} else {
containerVars.push(`background: ${banner};`);
}
}
if (headingVars.length || containerVars.length) {
const styles: string[] = ["<style>"];
if (headingVars.length) {
styles.push(`
.quarto-title-block .quarto-title-banner h1,
.quarto-title-block .quarto-title-banner h2,
.quarto-title-block .quarto-title-banner h3,
.quarto-title-block .quarto-title-banner h4,
.quarto-title-block .quarto-title-banner h5,
.quarto-title-block .quarto-title-banner h6
{
${headingVars.join("\n")}
}`);
}
if (containerVars.length) {
styles.push(`
.quarto-title-block .quarto-title-banner {
${containerVars.join("\n")}
}`);
}
styles.push("</style>");
const file = temp.createFile({ suffix: ".css" });
Deno.writeTextFileSync(file, styles.join("\n"));
return file;
}
}
export function documentTitlePartial(
format: Format,
) {
if (
format.metadata[kTitleBlockStyle] === false ||
format.metadata[kTitleBlockStyle] === "none"
) {
return {
partials: [],
templateParams: {},
};
} else {
const partials = [];
const templateParams: Metadata = {};
// Note whether we should be showing categories
templateParams[kTitleBlockCategories] =
format.metadata[kTitleBlockCategories] !== false ? "true" : "";
// Select the appropriate title block partial (banner vs no banner)
const banner = format.metadata[kTitleBlockBanner] as string | boolean;
const manuscriptTitle = format.metadata[kTitleBlockStyle] === "manuscript";
partials.push("_title-meta-author.html");
partials.push("title-metadata.html");
if (manuscriptTitle) {
partials.push("manuscript/title-block.html");
partials.push("manuscript/title-metadata.html");
} else if (banner) {
partials.push("banner/title-block.html");
} else {
partials.push("title-block.html");
}
// For banner partials, configure the options and pass them along in the metadata
if (banner || manuscriptTitle) {
// When the toc is on the left, be sure to add the special grid notation
const tocLeft = format.metadata[kTocLocation] === "left";
if (tocLeft) {
templateParams["banner-header-class"] = "toc-left";
}
}
return {
partials: partials.map((partial) => {
return formatResourcePath("html", join("templates", partial));
}),
templateParams,
};
}
}
export async function canonicalizeTitlePostprocessor(
doc: Document,
_options: {
inputMetadata: Metadata;
inputTraits: PandocInputTraits;
renderedFormats: RenderedFormat[];
quiet?: boolean;
},
): Promise<HtmlPostProcessResult> {
// https://github.com/quarto-dev/quarto-cli/issues/10567
// this fix cannot happen in `processDocumentTitle` because
// that's too late in the postprocessing order
const titleBlock = doc.querySelector("header.quarto-title-block");
const main = doc.querySelector("main");
// if no main element exists, this is likely a revealjs presentation
// which will generally have a title slide instead of a title block
// so we don't need to do anything
if (!titleBlock && main) {
const header = doc.createElement("header");
header.id = "title-block-header";
header.classList.add("quarto-title-block");
main.insertBefore(header, main.firstChild);
const h1s = Array.from(doc.querySelectorAll("h1"));
for (const h1n of h1s) {
const h1 = h1n as Element;
if (h1.classList.contains("quarto-secondary-nav-title")) {
continue;
}
// Now we need to check whether this is a plausible title element.
if (h1.parentElement?.tagName === "SECTION") {
// If the parent element is a section, then we need to check if there's
// any content before the section. If there is, then this is not a title
if (
h1.parentElement?.parentElement?.firstElementChild !==
h1.parentElement
) {
continue;
}
} else {
// If the parent element is not a section, then we need to check if there's
// any content before the h1. If there is, then this is not a title
if (h1.parentElement?.firstElementChild !== h1) {
continue;
}
}
const div = doc.createElement("div");
div.classList.add("quarto-title-banner");
h1.classList.add("title");
header.appendChild(h1);
break;
}
}
return {
resources: [],
supporting: [],
};
}
export function processDocumentTitle(
input: string,
format: Format,
_flags: PandocFlags,
doc: Document,
) {
const resources: string[] = [];
// when in banner mode, note on the main content region and
// add any image to resources
const banner = format.metadata[kTitleBlockBanner] as string | boolean;
const manuscriptTitle = format.metadata[kTitleBlockStyle] === "manuscript";
if (banner || manuscriptTitle) {
// Move the header above the content
const headerEl = doc.getElementById("title-block-header");
const contentEl = doc.getElementById("quarto-content");
if (contentEl && headerEl) {
headerEl.remove();
contentEl.parentElement?.insertBefore(headerEl, contentEl);
}
const mainEl = doc.querySelector("main.content");
mainEl?.classList.add("quarto-banner-title-block");
if (isBannerImage(input, banner)) {
resources.push(banner as string);
}
// Decorate the header
const quartoHeaderEl = doc.getElementById("quarto-header");
if (quartoHeaderEl) {
quartoHeaderEl.classList.add("quarto-banner");
}
}
return resources;
}
function isBannerImage(input: string, banner: unknown) {
if (typeof banner === "string") {
let path;
if (isAbsolute(banner)) {
path = banner;
} else {
path = join(dirname(input), banner);
}
return existsSync(path);
} else {
return false;
}
}
const titleColor = (block: unknown) => {
if (block === "body" || block === "body-bg") {
return undefined;
} else {
return block;
}
};
const _titleColorClass = (block: unknown) => {
if (block === "body") {
return "body";
} else if (block === "body-bg" || block === undefined) {
return "body-bg";
} else {
return "none";
}
};