starlight-cannoli-plugins 2.12.1 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -31
- package/dist/{chunk-OLF3SRCP.js → chunk-57RXO5LG.js} +194 -31
- package/dist/{chunk-NCXV367P.js → chunk-C2VXRQOK.js} +10823 -10844
- package/dist/{chunk-3GUF4GRX.js → chunk-JFHWCFHF.js} +1 -1
- package/dist/{chunk-TRGFYY3Y.js → chunk-O2TZASB7.js} +6 -1
- package/dist/{chunk-K65RRBFX.js → chunk-QPHWDN2I.js} +1 -1
- package/dist/chunk-WFN7OFMJ.js +28 -0
- package/dist/index.js +8 -6
- package/dist/plugins/astro-latex-compile.d.ts +1 -1
- package/dist/plugins/astro-latex-compile.js +2 -1
- package/dist/plugins/astro-sync-docs-to-public.js +3 -2
- package/dist/plugins/expressive-code-emphasis.js +1 -1
- package/dist/plugins/starlight-index-only-sidebar.js +3 -2
- package/dist/styles/_starlight.scss +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,6 @@ Automatically compiles fenced `tex compile` and `latex compile` code blocks to S
|
|
|
121
121
|
- Compiles LaTeX/TikZ code blocks to SVG automatically
|
|
122
122
|
- Caches compiled SVGs by content hash (no recompilation if unchanged)
|
|
123
123
|
- Comprehensive error reporting with line numbers and formatted LaTeX source
|
|
124
|
-
- Supports custom preamble via `% ===` separator in code blocks
|
|
125
124
|
- Works with Starlight and plain Astro projects
|
|
126
125
|
- Requires `svgOutputDir` configuration (no defaults)
|
|
127
126
|
|
|
@@ -178,46 +177,22 @@ Use either ` ```tex compile ` or ` ```latex compile ` — both work identically:
|
|
|
178
177
|
|
|
179
178
|
````markdown
|
|
180
179
|
```tex compile
|
|
180
|
+
\documentclass[border=5pt]{standalone}
|
|
181
|
+
\usepackage{tikz}
|
|
182
|
+
\begin{document}
|
|
183
|
+
\Large
|
|
181
184
|
\begin{tikzpicture}
|
|
182
185
|
\node (A) at (0,0) {A};
|
|
183
186
|
\node (B) at (2,0) {B};
|
|
184
187
|
\draw (A) -- (B);
|
|
185
188
|
\end{tikzpicture}
|
|
186
|
-
```
|
|
187
|
-
````
|
|
188
|
-
|
|
189
|
-
**Minimal approach:**
|
|
190
|
-
|
|
191
|
-
Use `% ===` to separate an optional custom preamble from your diagram content. The plugin wraps everything in the following document structure:
|
|
192
|
-
|
|
193
|
-
```latex
|
|
194
|
-
\documentclass[border=5pt]{standalone}
|
|
195
|
-
{your preamble}
|
|
196
|
-
\begin{document}
|
|
197
|
-
\Large
|
|
198
|
-
{your content}
|
|
199
189
|
\end{document}
|
|
200
190
|
```
|
|
201
|
-
|
|
202
|
-
Example **minimal** tex code block:
|
|
203
|
-
|
|
204
|
-
````markdown
|
|
205
|
-
```tex compile
|
|
206
|
-
\usepackage{tikz-3dplot}
|
|
207
|
-
|
|
208
|
-
% ===
|
|
209
|
-
|
|
210
|
-
\begin{tikzpicture}
|
|
211
|
-
% diagram code here
|
|
212
|
-
\end{tikzpicture}
|
|
213
|
-
```
|
|
214
191
|
````
|
|
215
192
|
|
|
216
|
-
|
|
193
|
+
**Document structure:**
|
|
217
194
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
If your code block contains both `\documentclass` and `\begin{document}`, the plugin treats it as a complete, self-contained LaTeX document and uses it as-is without checking for a `% ===` separator for a preamble:
|
|
195
|
+
Each code block must be a complete, self-contained LaTeX document:
|
|
221
196
|
|
|
222
197
|
````markdown
|
|
223
198
|
```tex compile
|
|
@@ -244,9 +219,14 @@ The following attributes can be added to the opening fence:
|
|
|
244
219
|
|
|
245
220
|
````markdown
|
|
246
221
|
```tex compile class="bg-white rounded-1" alt="A commutative diagram"
|
|
222
|
+
\documentclass[border=5pt]{standalone}
|
|
223
|
+
\usepackage{tikz}
|
|
224
|
+
\begin{document}
|
|
225
|
+
\Large
|
|
247
226
|
\begin{tikzpicture}
|
|
248
227
|
\node {Custom styled diagram};
|
|
249
228
|
\end{tikzpicture}
|
|
229
|
+
\end{document}
|
|
250
230
|
```
|
|
251
231
|
````
|
|
252
232
|
|
|
@@ -425,6 +405,50 @@ export default defineConfig({
|
|
|
425
405
|
});
|
|
426
406
|
```
|
|
427
407
|
|
|
408
|
+
### Expressive Code Emphasis
|
|
409
|
+
|
|
410
|
+
An Expressive Code plugin that highlights specific terms inside code blocks using the `emph` meta attribute. Matched terms are wrapped in a `<span class="fw-supreme">` for custom styling.
|
|
411
|
+
|
|
412
|
+
**Features:**
|
|
413
|
+
|
|
414
|
+
- Comma-separated list of terms to emphasize per code block
|
|
415
|
+
- Exact-word matching: terms surrounded by word boundaries or whitespace/string edges are matched; partial substrings are not
|
|
416
|
+
- Supports non-word characters (e.g. `(`, `]`, `;`) as well as identifiers
|
|
417
|
+
|
|
418
|
+
**Usage:**
|
|
419
|
+
|
|
420
|
+
Register the plugin with Expressive Code:
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
// astro.config.mjs
|
|
424
|
+
import { defineConfig } from "astro/config";
|
|
425
|
+
import starlight from "@astrojs/starlight";
|
|
426
|
+
import { expressiveCodeEmphasis } from "starlight-cannoli-plugins";
|
|
427
|
+
|
|
428
|
+
export default defineConfig({
|
|
429
|
+
integrations: [
|
|
430
|
+
starlight({
|
|
431
|
+
title: "My Docs",
|
|
432
|
+
expressiveCode: {
|
|
433
|
+
plugins: [expressiveCodeEmphasis()],
|
|
434
|
+
},
|
|
435
|
+
}),
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Then use the `emph` meta attribute on any code block:
|
|
441
|
+
|
|
442
|
+
````markdown
|
|
443
|
+
```js emph="foo,bar,()"
|
|
444
|
+
function foo() {
|
|
445
|
+
return bar() || ();
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
````
|
|
449
|
+
|
|
450
|
+
Multiple terms are separated by commas. Each term is matched as a whole unit — it must be surrounded by whitespace, start/end of line, or (for terms that start/end with word characters) word boundaries.
|
|
451
|
+
|
|
428
452
|
## CLI Utilities
|
|
429
453
|
|
|
430
454
|
### cannoli-latex-cleanup
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseFrontmatter
|
|
3
|
+
} from "./chunk-C2VXRQOK.js";
|
|
4
|
+
|
|
1
5
|
// src/plugins/astro-latex-compile/index.ts
|
|
6
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2
7
|
import { rm } from "fs/promises";
|
|
3
|
-
import { join as join2, resolve as resolve2 } from "path";
|
|
8
|
+
import { join as join2, relative as relative2, resolve as resolve2 } from "path";
|
|
4
9
|
import { visit, SKIP } from "unist-util-visit";
|
|
5
10
|
import { MetaOptions } from "@expressive-code/core";
|
|
6
11
|
|
|
@@ -9,11 +14,13 @@ import { createHash } from "crypto";
|
|
|
9
14
|
import {
|
|
10
15
|
existsSync,
|
|
11
16
|
mkdirSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
readFileSync,
|
|
12
19
|
writeFileSync,
|
|
13
20
|
rmSync,
|
|
14
21
|
mkdtempSync
|
|
15
22
|
} from "fs";
|
|
16
|
-
import { basename, dirname, join, resolve } from "path";
|
|
23
|
+
import { basename, dirname, join, relative, resolve } from "path";
|
|
17
24
|
import { tmpdir } from "os";
|
|
18
25
|
import sharp from "sharp";
|
|
19
26
|
|
|
@@ -187,6 +194,61 @@ function execProcess(command, args, options) {
|
|
|
187
194
|
});
|
|
188
195
|
}
|
|
189
196
|
|
|
197
|
+
// src/plugins/utils/html-builder.ts
|
|
198
|
+
import { JSDOM } from "jsdom";
|
|
199
|
+
var { document } = new JSDOM("").window;
|
|
200
|
+
var HtmlSanitizer = {
|
|
201
|
+
tempElement: document.createElement("div"),
|
|
202
|
+
sanitize(htmlString) {
|
|
203
|
+
this.tempElement.textContent = htmlString;
|
|
204
|
+
return this.tempElement.innerHTML;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var HtmlString = class extends String {
|
|
208
|
+
element = null;
|
|
209
|
+
constructor(value) {
|
|
210
|
+
super(value);
|
|
211
|
+
}
|
|
212
|
+
asElement() {
|
|
213
|
+
if (this.element !== null) {
|
|
214
|
+
return this.element;
|
|
215
|
+
}
|
|
216
|
+
const temp = document.createElement("div");
|
|
217
|
+
temp.innerHTML = this.valueOf();
|
|
218
|
+
if (temp.childElementCount > 1) {
|
|
219
|
+
throw new Error("html template does not accept more than 1 element");
|
|
220
|
+
}
|
|
221
|
+
const child = temp.firstElementChild;
|
|
222
|
+
if (child === null) {
|
|
223
|
+
throw new Error("html template produced no elements");
|
|
224
|
+
}
|
|
225
|
+
this.element = child;
|
|
226
|
+
return this.element;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function html(literalValues, ...interpolatedValues) {
|
|
230
|
+
let result = "";
|
|
231
|
+
interpolatedValues.forEach((currentInterpolatedVal, idx) => {
|
|
232
|
+
const literalVal = literalValues[idx];
|
|
233
|
+
let interpolatedVal = "";
|
|
234
|
+
if (Array.isArray(currentInterpolatedVal)) {
|
|
235
|
+
interpolatedVal = currentInterpolatedVal.join("\n");
|
|
236
|
+
} else if (typeof currentInterpolatedVal !== "boolean") {
|
|
237
|
+
interpolatedVal = currentInterpolatedVal.toString();
|
|
238
|
+
}
|
|
239
|
+
const isSanitize = !literalVal.endsWith("$");
|
|
240
|
+
if (isSanitize) {
|
|
241
|
+
result += literalVal;
|
|
242
|
+
result += HtmlSanitizer.sanitize(interpolatedVal);
|
|
243
|
+
} else {
|
|
244
|
+
result += literalVal.slice(0, -1);
|
|
245
|
+
result += interpolatedVal;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
result += literalValues.slice(-1);
|
|
249
|
+
return new HtmlString(result);
|
|
250
|
+
}
|
|
251
|
+
|
|
190
252
|
// src/plugins/astro-latex-compile/utils.ts
|
|
191
253
|
var CONTENT_ROOT = "src/content/docs/";
|
|
192
254
|
function computeJpgPath(tempOutputDir, filePath, blockId) {
|
|
@@ -201,7 +263,7 @@ function computeJpgPath(tempOutputDir, filePath, blockId) {
|
|
|
201
263
|
}
|
|
202
264
|
async function writeJpgFromSvg(svgPath, jpgPath) {
|
|
203
265
|
mkdirSync(dirname(jpgPath), { recursive: true });
|
|
204
|
-
await sharp(svgPath).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
266
|
+
await sharp(svgPath, { density: 300 }).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
205
267
|
}
|
|
206
268
|
function stripAnsi(text) {
|
|
207
269
|
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -254,33 +316,107 @@ async function writeJpgError(jpgPath, header, errorText) {
|
|
|
254
316
|
mkdirSync(dirname(jpgPath), { recursive: true });
|
|
255
317
|
await sharp(Buffer.from(svg)).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
256
318
|
}
|
|
257
|
-
function
|
|
319
|
+
function computeLineOffset(_latexCode) {
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
function buildErrorHtml(header, errorMsg, latexCode) {
|
|
323
|
+
const clean = stripAnsi(errorMsg);
|
|
324
|
+
const sourceIdx = clean.indexOf("LaTeX source:");
|
|
325
|
+
const summary = (sourceIdx !== -1 ? clean.slice(0, sourceIdx) : clean).trimEnd();
|
|
326
|
+
const compiledLineNums = [];
|
|
327
|
+
const linePattern = /Error \(line (\d+)\)/g;
|
|
328
|
+
let m;
|
|
329
|
+
while ((m = linePattern.exec(summary)) !== null) {
|
|
330
|
+
compiledLineNums.push(parseInt(m[1], 10));
|
|
331
|
+
}
|
|
332
|
+
const offset = computeLineOffset(latexCode);
|
|
333
|
+
const errorLineSet = new Set(
|
|
334
|
+
compiledLineNums.map((n) => n - offset).filter((n) => n > 0)
|
|
335
|
+
);
|
|
336
|
+
const codeLines = latexCode.split("\n");
|
|
337
|
+
const lineWidth = String(codeLines.length).length;
|
|
338
|
+
const summaryLines = summary.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => html`<div>${line}</div>`);
|
|
339
|
+
const codeBlock = new HtmlString(
|
|
340
|
+
codeLines.map((line, i) => {
|
|
341
|
+
const lineNum = i + 1;
|
|
342
|
+
const isError = errorLineSet.has(lineNum);
|
|
343
|
+
const gutter = String(lineNum).padStart(lineWidth);
|
|
344
|
+
const bg = isError ? "background:var(--cannoli-error-low);" : "";
|
|
345
|
+
return html`<span style="${bg}display:block;padding:0 14px"
|
|
346
|
+
><span style="color:var(--sl-color-gray-3);user-select:none"
|
|
347
|
+
>${gutter} │ </span
|
|
348
|
+
>${line}</span
|
|
349
|
+
>`.toString();
|
|
350
|
+
}).join("")
|
|
351
|
+
);
|
|
352
|
+
return html`<div
|
|
353
|
+
class="not-content"
|
|
354
|
+
style="border:1px solid var(--cannoli-error-low);border-radius:6px;overflow:hidden;margin:1.5em 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;line-height:1.5;display:flex;flex-direction:column"
|
|
355
|
+
>
|
|
356
|
+
<div
|
|
357
|
+
style="background:var(--cannoli-error);color:var(--sl-color-black);padding:8px 14px;font-weight:700"
|
|
358
|
+
>
|
|
359
|
+
⚠ LaTeX Compilation Error — ${header}
|
|
360
|
+
</div>
|
|
361
|
+
<div
|
|
362
|
+
style="background:var(--cannoli-error-low);color:var(--cannoli-error-high);padding:10px 14px;border-bottom:1px solid var(--cannoli-error)"
|
|
363
|
+
>
|
|
364
|
+
$${summaryLines}
|
|
365
|
+
</div>
|
|
366
|
+
<pre
|
|
367
|
+
style="margin:0;background:var(--sl-color-gray-6);color:var(--sl-color-text);overflow-x:auto"
|
|
368
|
+
><code style="display:block;padding:8px 0;font-size:12px">$${codeBlock}</code></pre>
|
|
369
|
+
</div>`.toString();
|
|
370
|
+
}
|
|
371
|
+
function collectTexInputFiles(dir, recursive) {
|
|
372
|
+
const files = [];
|
|
373
|
+
try {
|
|
374
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
375
|
+
const full = join(dir, entry.name);
|
|
376
|
+
if (entry.isDirectory() && recursive) {
|
|
377
|
+
files.push(...collectTexInputFiles(full, recursive));
|
|
378
|
+
} else if (entry.isFile()) {
|
|
379
|
+
files.push(full);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
return files.sort();
|
|
385
|
+
}
|
|
386
|
+
function computeTexInputDirsSalt(texInputDirs) {
|
|
387
|
+
if (texInputDirs.length === 0) return "";
|
|
388
|
+
const hash = createHash("md5");
|
|
389
|
+
let hasAnyFile = false;
|
|
390
|
+
for (const dir of texInputDirs) {
|
|
391
|
+
const recursive = dir.endsWith("//");
|
|
392
|
+
const resolved = resolve(dir.endsWith("/") ? dir.slice(0, -1) : dir);
|
|
393
|
+
for (const filePath of collectTexInputFiles(resolved, recursive)) {
|
|
394
|
+
try {
|
|
395
|
+
hash.update(relative(process.cwd(), filePath));
|
|
396
|
+
hash.update(readFileSync(filePath));
|
|
397
|
+
hasAnyFile = true;
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return hasAnyFile ? hash.digest("hex").slice(0, 16) : "";
|
|
403
|
+
}
|
|
404
|
+
function hashLatexCode(code, salt = "") {
|
|
258
405
|
const normalized = code.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("%")).filter(Boolean).join("\n").trim();
|
|
259
|
-
|
|
406
|
+
const h = createHash("md5").update(normalized);
|
|
407
|
+
if (salt) h.update(salt);
|
|
408
|
+
return h.digest("hex").slice(0, 16);
|
|
260
409
|
}
|
|
261
410
|
function buildLatexSource(latexCode) {
|
|
262
411
|
if (latexCode.includes("\\documentclass") && latexCode.includes("\\begin{document}")) {
|
|
263
412
|
return latexCode.trim();
|
|
264
413
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
let content = latexCode.trim();
|
|
269
|
-
if (parts.length === 2) {
|
|
270
|
-
preamble = parts[0].trim();
|
|
271
|
-
content = parts[1].trim();
|
|
272
|
-
}
|
|
273
|
-
return [
|
|
274
|
-
"\\documentclass[border=5pt]{standalone}",
|
|
275
|
-
preamble,
|
|
276
|
-
"\\begin{document}",
|
|
277
|
-
"\\Large",
|
|
278
|
-
content,
|
|
279
|
-
"\\end{document}"
|
|
280
|
-
].join("\n");
|
|
414
|
+
throw new Error(
|
|
415
|
+
`[remark-latex-compile] Code block is not a complete LaTeX document. Blocks must contain both \\documentclass and \\begin{document}.`
|
|
416
|
+
);
|
|
281
417
|
}
|
|
282
|
-
async function compileLatexToSvg(latexCode, svgOutputDir, texInputDirs = []) {
|
|
283
|
-
const hash = hashLatexCode(latexCode);
|
|
418
|
+
async function compileLatexToSvg(latexCode, svgOutputDir, texInputDirs = [], inputsSalt = "") {
|
|
419
|
+
const hash = hashLatexCode(latexCode, inputsSalt);
|
|
284
420
|
const svgPath = join(svgOutputDir, `${hash}.svg`);
|
|
285
421
|
if (existsSync(svgPath)) {
|
|
286
422
|
return { hash, svgPath, wasCompiled: false };
|
|
@@ -351,6 +487,16 @@ Error: ${errorOutput}`
|
|
|
351
487
|
}
|
|
352
488
|
|
|
353
489
|
// src/plugins/astro-latex-compile/index.ts
|
|
490
|
+
function getFrontmatterOffset(absoluteFilePath) {
|
|
491
|
+
try {
|
|
492
|
+
const original = readFileSync2(absoluteFilePath, "utf-8");
|
|
493
|
+
const { rawFrontmatter } = parseFrontmatter(original);
|
|
494
|
+
if (!rawFrontmatter) return 0;
|
|
495
|
+
return rawFrontmatter.split("\n").length - 1 + 2;
|
|
496
|
+
} catch {
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
354
500
|
function remarkLatexCompile(options) {
|
|
355
501
|
const svgOutputDir = resolve2(options.svgOutputDir);
|
|
356
502
|
return async function transformer(tree, file) {
|
|
@@ -363,27 +509,31 @@ function remarkLatexCompile(options) {
|
|
|
363
509
|
});
|
|
364
510
|
if (nodes.length === 0) return;
|
|
365
511
|
const filePath = file.path || "unknown";
|
|
512
|
+
const relFilePath = filePath !== "unknown" ? relative2(process.cwd(), filePath) : "unknown";
|
|
513
|
+
const frontmatterOffset = filePath !== "unknown" ? getFrontmatterOffset(filePath) : 0;
|
|
514
|
+
const inputsSalt = computeTexInputDirsSalt(options.texInputDirs ?? []);
|
|
366
515
|
const results = await Promise.all(
|
|
367
516
|
nodes.map(async ({ node, index, parent }) => {
|
|
368
|
-
const lineNumberStr = node.position?.start.line
|
|
517
|
+
const lineNumberStr = node.position?.start.line !== void 0 ? String(node.position.start.line + frontmatterOffset) : "?";
|
|
369
518
|
const blockId = new MetaOptions(node.meta ?? "").getInteger("blockid");
|
|
370
519
|
const jpgPath = options.tempOutputDir && blockId !== void 0 ? computeJpgPath(options.tempOutputDir, filePath, blockId) : null;
|
|
371
520
|
try {
|
|
372
521
|
const result = await compileLatexToSvg(
|
|
373
522
|
node.value,
|
|
374
523
|
svgOutputDir,
|
|
375
|
-
options.texInputDirs ?? []
|
|
524
|
+
options.texInputDirs ?? [],
|
|
525
|
+
inputsSalt
|
|
376
526
|
);
|
|
377
527
|
if (result.wasCompiled) {
|
|
378
528
|
console.log(
|
|
379
|
-
`[remark-latex-compile] ${
|
|
529
|
+
`[remark-latex-compile] ${relFilePath}:${lineNumberStr}: compiled ${result.hash}.svg`
|
|
380
530
|
);
|
|
381
531
|
}
|
|
382
532
|
options._referencedHashes?.add(result.hash);
|
|
383
533
|
if (jpgPath) {
|
|
384
534
|
await writeJpgFromSvg(result.svgPath, jpgPath);
|
|
385
535
|
console.log(
|
|
386
|
-
`[remark-latex-compile] ${
|
|
536
|
+
`[remark-latex-compile] ${relFilePath}:${lineNumberStr}: wrote ${jpgPath}`
|
|
387
537
|
);
|
|
388
538
|
}
|
|
389
539
|
return {
|
|
@@ -399,13 +549,13 @@ function remarkLatexCompile(options) {
|
|
|
399
549
|
const match = errorMsg.match(/\n\n([\s\S]+)/);
|
|
400
550
|
const details = match ? match[1] : errorMsg;
|
|
401
551
|
console.error(
|
|
402
|
-
`[remark-latex-compile] ${
|
|
552
|
+
`[remark-latex-compile] ${relFilePath}:${lineNumberStr}
|
|
403
553
|
${details}`
|
|
404
554
|
);
|
|
405
555
|
if (jpgPath) {
|
|
406
556
|
await writeJpgError(
|
|
407
557
|
jpgPath,
|
|
408
|
-
`${
|
|
558
|
+
`${relFilePath}:${lineNumberStr}`,
|
|
409
559
|
errorMsg
|
|
410
560
|
);
|
|
411
561
|
}
|
|
@@ -453,9 +603,22 @@ ${details}`
|
|
|
453
603
|
options._fileJpgPathMap.set(filePath, newJpgPaths);
|
|
454
604
|
}
|
|
455
605
|
for (let i = results.length - 1; i >= 0; i--) {
|
|
456
|
-
const { index, parent, result } = results[i];
|
|
606
|
+
const { index, parent, result, error } = results[i];
|
|
457
607
|
const { node } = nodes[i];
|
|
458
|
-
if (!result)
|
|
608
|
+
if (!result) {
|
|
609
|
+
const lineNumberStr = node.position?.start.line !== void 0 ? String(node.position.start.line + frontmatterOffset) : "?";
|
|
610
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
611
|
+
const errorNode = {
|
|
612
|
+
type: "html",
|
|
613
|
+
value: buildErrorHtml(
|
|
614
|
+
`${relFilePath}:${lineNumberStr}`,
|
|
615
|
+
errorMsg,
|
|
616
|
+
node.value
|
|
617
|
+
)
|
|
618
|
+
};
|
|
619
|
+
parent.children.splice(index, 1, errorNode);
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
459
622
|
const metaOptions = new MetaOptions(node.meta ?? "");
|
|
460
623
|
const customClasses = metaOptions.getString("class")?.split(/\s+/).filter(Boolean) ?? [];
|
|
461
624
|
const altText = metaOptions.getString("alt") ?? "LaTeX diagram";
|