Skip to content

Commit 2170bd2

Browse files
committed
Merge branch 'main' into windows/bib-forward-slash
2 parents 401b9e2 + 3258068 commit 2170bd2

File tree

16 files changed

+335
-210
lines changed

16 files changed

+335
-210
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ print("Hello Quarto!")
2222

2323
I have (if applicable):
2424

25-
- [ ] filed a [contributor agreement](../CONTRIBUTING.md).
25+
- [ ] filed a [contributor agreement](https://github.com/quarto-dev/quarto-cli/blob/main/CONTRIBUTING.md).
2626
- [ ] referenced the GitHub issue this PR closes
2727
- [ ] updated the appropriate changelog

news/changelog-1.3.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- Add support for automatically converting SVG images to PDF ([#2575](https://github.com/quarto-dev/quarto-cli/issues/2575))
3737
- Previously, if the `pdf-engine` was set to `latexmk`, we would bypass many features of Quarto and use Pandoc to produce the PDF output. Starting in in Quarto 1.3, all Quarto features will be enabled for the `latexmk` engine and `latexmk` will be used to run the PDF generation loop.
3838
- Fix author processing in default PDFs for complex author names (#3483)
39+
- Remove excessive vertical space between theorem type blocks ([#3776](https://github.com/quarto-dev/quarto-cli/issues/3776)).
3940

4041
## Beamer Format
4142

src/command/render/render.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { inputFilesDir } from "../../core/render.ts";
2121
import { pathWithForwardSlashes } from "../../core/path.ts";
2222

23-
import { Format, FormatPandoc } from "../../config/types.ts";
23+
import { FormatPandoc } from "../../config/types.ts";
2424
import {
2525
executionEngine,
2626
executionEngineKeepMd,
@@ -121,6 +121,11 @@ export async function renderPandoc(
121121
executeResult.markdown,
122122
);
123123

124+
if (notebookResult.supporting) {
125+
executeResult.supporting = executeResult.supporting || [];
126+
executeResult.supporting.push(notebookResult.supporting);
127+
}
128+
124129
// Map notebook includes to pandoc includes
125130
const pandocIncludes: PandocIncludes = {
126131
[kIncludeAfterBody]: notebookResult.includes?.afterBody
@@ -139,7 +144,9 @@ export async function renderPandoc(
139144

140145
// pandoc options
141146
const pandocOptions: PandocOptions = {
142-
markdown: notebookResult.markdown,
147+
markdown: notebookResult.markdown
148+
? notebookResult.markdown
149+
: executeResult.markdown,
143150
source: context.target.source,
144151
output: recipe.output,
145152
keepYaml: recipe.keepYaml,

src/core/jupyter/jupyter-embed.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { resourcePath } from "../resources.ts";
99
import { getNamedLifetime, ObjectWithLifetime } from "../lifetimes.ts";
1010

1111
import {
12+
cleanEmptyJupyterAssets,
1213
jupyterAssets,
1314
jupyterFromFile,
1415
jupyterToMarkdown,
@@ -44,7 +45,7 @@ import {
4445
import { globalTempContext } from "../temp.ts";
4546
import { isAbsolute } from "path/mod.ts";
4647
import { partitionMarkdown } from "../pandoc/pandoc-partition.ts";
47-
import { safeExistsSync } from "../path.ts";
48+
import { removeIfEmptyDir, safeExistsSync } from "../path.ts";
4849
import { basename } from "path/mod.ts";
4950

5051
export interface JupyterNotebookAddress {
@@ -149,8 +150,16 @@ export async function replaceNotebookPlaceholders(
149150
markdown: string,
150151
) {
151152
let match = kPlaceholderRegex.exec(markdown);
153+
let assets;
152154
let includes;
153155
while (match) {
156+
if (!assets) {
157+
assets = jupyterAssets(
158+
context.target.source,
159+
to,
160+
);
161+
}
162+
154163
// Parse the address and if this is a notebook
155164
// then proceed with the replacement
156165
const nbAddressStr = match[1];
@@ -166,12 +175,6 @@ export async function replaceNotebookPlaceholders(
166175
? placeholderToOptions(placeholderStr)
167176
: {};
168177

169-
// Assets
170-
const assets = jupyterAssets(
171-
input,
172-
to,
173-
);
174-
175178
// Compute appropriate includes based upon the note
176179
// dependendencies
177180
const notebookIncludes = () => {
@@ -215,9 +218,13 @@ export async function replaceNotebookPlaceholders(
215218
match = kPlaceholderRegex.exec(markdown);
216219
}
217220
kPlaceholderRegex.lastIndex = 0;
221+
const supporting = assets
222+
? join(assets.base_dir, assets.supporting_dir)
223+
: undefined;
218224
return {
219225
includes,
220226
markdown,
227+
supporting,
221228
};
222229
}
223230

src/core/jupyter/jupyter.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,12 @@ import { figuresDir, inputFilesDir } from "../render.ts";
145145
import { lines } from "../text.ts";
146146
import { readYamlFromMarkdown } from "../yaml.ts";
147147
import { languagesInMarkdown } from "../../execute/engine-shared.ts";
148-
import { pathWithForwardSlashes } from "../path.ts";
148+
import { pathWithForwardSlashes, removeIfEmptyDir } from "../path.ts";
149149
import { convertToHtmlSpans, hasAnsiEscapeCodes } from "../ansi-colors.ts";
150150
import { ProjectContext } from "../../project/types.ts";
151151
import { mergeConfigs } from "../config.ts";
152152
import { encode as encodeBase64 } from "encoding/base64.ts";
153+
import { isIpynbOutput } from "../../config/format.ts";
153154

154155
export const kQuartoMimeType = "quarto_mimetype";
155156
export const kQuartoOutputOrder = "quarto_order";
@@ -571,7 +572,17 @@ export function jupyterAutoIdentifier(label: string) {
571572
}
572573
}
573574

574-
export function jupyterAssets(input: string, to?: string) {
575+
export interface JupyterNotebookAssetPaths {
576+
base_dir: string;
577+
files_dir: string;
578+
figures_dir: string;
579+
supporting_dir: string;
580+
}
581+
582+
export function jupyterAssets(
583+
input: string,
584+
to?: string,
585+
): JupyterNotebookAssetPaths {
575586
// calculate and create directories
576587
input = Deno.realPathSync(input);
577588
const files_dir = join(dirname(input), inputFilesDir(input));
@@ -601,6 +612,16 @@ export function jupyterAssets(input: string, to?: string) {
601612
};
602613
}
603614

615+
export function cleanEmptyJupyterAssets(assets: JupyterNotebookAssetPaths) {
616+
const figuresRemoved = removeIfEmptyDir(
617+
join(assets.base_dir, assets.figures_dir),
618+
);
619+
const filesRemoved = removeIfEmptyDir(
620+
join(assets.base_dir, assets.files_dir),
621+
);
622+
return figuresRemoved && filesRemoved;
623+
}
624+
604625
// Attach fully rendered notebook to render services
605626
// Render notebook only once per document
606627
// Return cells with markdown instead of complete markdown
@@ -1096,6 +1117,14 @@ async function mdFromCodeCell(
10961117
// write div enclosure
10971118
const divMd: string[] = [`::: {`];
10981119

1120+
// If we're targeting ipynb output, include the id in the
1121+
// markdown. This will cause the id to be included in the
1122+
// rendered notebook. Note that elsewhere we forard the
1123+
// label to the id, so that can appear as the cell id.
1124+
if (isIpynbOutput(options.executeOptions.format.pandoc) && cell.id) {
1125+
divMd.push(`#${cell.id} `);
1126+
}
1127+
10991128
// metadata to exclude from cell div attributes
11001129
const kCellOptionsFilter = kJupyterCellInternalOptionKeys.concat(
11011130
kJupyterCellStandardMetadataKeys,

src/core/path.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function safeRemoveIfExists(file: string) {
4040
}
4141
}
4242

43-
export function removeIfEmptyDir(dir: string) {
43+
export function removeIfEmptyDir(dir: string): boolean {
4444
if (existsSync(dir)) {
4545
let empty = true;
4646
for (const _entry of Deno.readDirSync(dir)) {
@@ -49,7 +49,11 @@ export function removeIfEmptyDir(dir: string) {
4949
}
5050
if (empty) {
5151
Deno.removeSync(dir, { recursive: true });
52+
return true;
5253
}
54+
return false;
55+
} else {
56+
return false;
5357
}
5458
}
5559

src/core/sass.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { dartCompile } from "./dart-sass.ts";
1717
import * as ld from "./lodash.ts";
1818
import { lines } from "./text.ts";
1919
import { md5Hash } from "./hash.ts";
20+
import { debug } from "log/mod.ts";
2021

2122
export interface SassVariable {
2223
name: string;
@@ -314,9 +315,13 @@ export async function compileWithCache(
314315
let cacheIndex: { [key: string]: { key: string; hash: string } } = {};
315316
let writeCache = true;
316317
if (existsSync(outputFilePath)) {
317-
cacheIndex = JSON.parse(Deno.readTextFileSync(cacheIdxPath));
318-
const existingEntry = cacheIndex[identifierHash];
319-
writeCache = !existingEntry || (existingEntry.hash !== inputHash);
318+
try {
319+
cacheIndex = JSON.parse(Deno.readTextFileSync(cacheIdxPath));
320+
const existingEntry = cacheIndex[identifierHash];
321+
writeCache = !existingEntry || (existingEntry.hash !== inputHash);
322+
} catch {
323+
debug(`The scss cache index file ${cacheIdxPath} can't be read.`);
324+
}
320325
}
321326

322327
// We need to refresh the cache

src/format/html/format-html-bootstrap.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ function bootstrapHtmlPostprocessor(
246246
): Promise<HtmlPostProcessResult> => {
247247
// Resources used in this post processor
248248
const resources: string[] = [];
249+
const supporting: string[] = [];
249250

250251
// use display-7 style for title
251252
const title = doc.querySelector("header > .title");
@@ -334,7 +335,16 @@ function bootstrapHtmlPostprocessor(
334335

335336
// Look for included / embedded notebooks and include those
336337
if (format.render[kNotebookLinks] !== false) {
337-
await processNotebookEmbeds(input, doc, format, resources, services);
338+
const notebookPreviews = await processNotebookEmbeds(
339+
input,
340+
doc,
341+
format,
342+
resources,
343+
services,
344+
);
345+
if (notebookPreviews && notebookPreviews.length > 0) {
346+
supporting.push(...notebookPreviews);
347+
}
338348
}
339349

340350
// default treatment for computational tables
@@ -421,7 +431,7 @@ function bootstrapHtmlPostprocessor(
421431
}
422432

423433
// no resource refs
424-
return Promise.resolve({ resources, supporting: [] });
434+
return Promise.resolve({ resources, supporting });
425435
};
426436
}
427437

src/format/html/format-html-notebook.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ import {
2626
HtmlPostProcessResult,
2727
RenderServices,
2828
} from "../../command/render/types.ts";
29-
import { render } from "../../command/render/render-shared.ts";
3029

3130
import { basename, dirname, join, relative } from "path/mod.ts";
31+
import { renderFiles } from "../../command/render/render-files.ts";
3232

3333
interface NotebookView {
3434
title: string;
@@ -226,6 +226,11 @@ export async function processNotebookEmbeds(
226226
if (nbViewConfig) {
227227
nbViewConfig.unused(linkedNotebooks);
228228
}
229+
230+
const inputDir = dirname(input);
231+
return nbPaths.map((nbPath) => {
232+
return join(inputDir, nbPath.href);
233+
});
229234
}
230235
}
231236

@@ -288,7 +293,7 @@ async function renderHtmlView(
288293

289294
// Render the notebook and update the path
290295
const nbPreviewFile = `${filename}.html`;
291-
await render(nbAbsPath, {
296+
await renderFiles([{ path: nbAbsPath }], {
292297
services,
293298
flags: {
294299
metadata: {

src/format/html/format-html.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,22 @@ function processCodeAnnotations(format: Format, doc: Document) {
714714
| string
715715
| boolean;
716716

717+
const replaceLineNumberWithAnnote = (annoteEl: Element, dtEl: Element) => {
718+
const annotation = annoteEl.getAttribute(kCodeAnnotationAttr);
719+
if (annotation !== null) {
720+
const ddEl = dtEl.previousElementSibling;
721+
if (ddEl) {
722+
ddEl.innerHTML = "";
723+
ddEl.innerText = annotation;
724+
const codeCell = annoteEl.getAttribute(kCodeCellAttr);
725+
if (codeCell) {
726+
ddEl.setAttribute(kCodeCellTargetAttr, codeCell);
727+
ddEl.setAttribute(kCodeAnnotationTargetAttr, annotation);
728+
}
729+
}
730+
}
731+
};
732+
717733
if (annotationStyle === false) {
718734
// Read the definition list values which contain the annotations
719735
const annoteNodes = doc.querySelectorAll(`span[${kCodeCellAttr}]`);
@@ -732,26 +748,23 @@ function processCodeAnnotations(format: Format, doc: Document) {
732748
}
733749
}
734750
} else if (annotationStyle === "hover" || annotationStyle === "select") {
735-
const definitionLists = processCodeBlockAnnotation(doc, true, "start");
751+
const definitionLists = processCodeBlockAnnotation(
752+
doc,
753+
true,
754+
"start",
755+
replaceLineNumberWithAnnote,
756+
);
736757

737758
Object.values(definitionLists).forEach((dl) => {
738759
dl.classList.add(kCodeAnnotationHiddenClz);
760+
dl.classList.add(kCodeAnnotationGridClz);
739761
});
740762
} else {
741763
const definitionLists = processCodeBlockAnnotation(
742764
doc,
743765
false,
744766
"start",
745-
(annoteEl: Element, dtEl: Element) => {
746-
const annotation = annoteEl.getAttribute(kCodeAnnotationAttr);
747-
if (annotation !== null) {
748-
const ddEl = dtEl.previousElementSibling;
749-
if (ddEl) {
750-
ddEl.innerHTML = "";
751-
ddEl.innerText = annotation;
752-
}
753-
}
754-
},
767+
replaceLineNumberWithAnnote,
755768
);
756769

757770
Object.values(definitionLists).forEach((dl) => {

0 commit comments

Comments
 (0)